brut 0.0.29 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +23 -2
- data/assets/LogoStop.pxd +0 -0
- data/assets/MetroLogo.graffle +0 -0
- data/assets/SocialImage.png +0 -0
- data/assets/SocialImage.pxd +0 -0
- data/brutrb.com/.vitepress/config.mjs +48 -9
- data/brutrb.com/.vitepress/theme/style.css +14 -35
- data/brutrb.com/adrs.md +15 -0
- data/brutrb.com/ai.md +10 -15
- data/brutrb.com/assets.md +2 -9
- data/brutrb.com/brut-js.md +12 -2
- data/brutrb.com/cli.md +9 -13
- data/brutrb.com/components.md +118 -96
- data/brutrb.com/configuration.md +3 -4
- data/brutrb.com/css.md +2 -2
- data/brutrb.com/custom-element-tests.md +3 -4
- data/brutrb.com/database-access.md +1 -1
- data/brutrb.com/database-schema.md +29 -41
- data/brutrb.com/dev-environment.md +13 -8
- data/brutrb.com/dir-structure.md +120 -0
- data/brutrb.com/doc-conventions.md +17 -15
- data/brutrb.com/dx +1 -0
- data/brutrb.com/end-to-end-tests.md +12 -10
- data/brutrb.com/features.md +373 -0
- data/brutrb.com/flash-and-session.md +115 -131
- data/brutrb.com/form-constraints.md +266 -0
- data/brutrb.com/forms.md +140 -765
- data/brutrb.com/getting-started.md +10 -11
- data/brutrb.com/handlers.md +119 -95
- data/brutrb.com/hooks.md +18 -20
- data/brutrb.com/i18n.md +6 -4
- data/brutrb.com/images/DevEnvironment.graffle +0 -0
- data/brutrb.com/images/DevEnvironment.png +0 -0
- data/brutrb.com/images/LogoStop.png +0 -0
- data/brutrb.com/index.md +0 -3
- data/brutrb.com/instrumentation.md +7 -10
- data/brutrb.com/javascript.md +14 -14
- data/brutrb.com/keyword-injection.md +72 -114
- data/brutrb.com/layouts.md +20 -52
- data/brutrb.com/lsp.md +1 -1
- data/brutrb.com/overview.md +30 -372
- data/brutrb.com/pages.md +119 -207
- data/brutrb.com/public/SocialImage.png +0 -0
- data/brutrb.com/public/favicon.ico +0 -0
- data/brutrb.com/recipes/alternate-layouts.md +32 -0
- data/brutrb.com/recipes/authentication.md +315 -6
- data/brutrb.com/recipes/blank-layouts.md +22 -0
- data/brutrb.com/recipes/custom-flash.md +51 -0
- data/brutrb.com/recipes/indexed-forms.md +149 -0
- data/brutrb.com/recipes/text-field-component.md +182 -0
- data/brutrb.com/roadmap.md +57 -0
- data/brutrb.com/routes.md +56 -82
- data/brutrb.com/security.md +0 -3
- data/brutrb.com/space-time-continuum.md +8 -12
- data/brutrb.com/tutorial.md +5 -1
- data/brutrb.com/why.md +19 -0
- data/docs/404.html +8 -3
- data/docs/SocialImage.png +0 -0
- data/docs/adrs.html +29 -0
- data/docs/ai.html +11 -6
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +3 -3
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +6 -6
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +2 -2
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +3 -3
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +9 -11
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +135 -20
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +135 -20
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +2 -2
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/RichString.html +150 -343
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +5 -5
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +22 -3
- data/docs/api/index.html +22 -3
- data/docs/api/method_list.html +290 -306
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/DevEnvironment.DaFcVfwP.png +0 -0
- data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
- data/docs/assets/adrs.md.JRxZ5uYE.js +1 -0
- data/docs/assets/adrs.md.JRxZ5uYE.lean.js +1 -0
- data/docs/assets/{ai.md._6HCDL6d.js → ai.md.Cy9GWnER.js} +1 -1
- data/docs/assets/ai.md.Cy9GWnER.lean.js +1 -0
- data/docs/assets/{app.BhrfSt68.js → app.Dm3x-DQc.js} +1 -1
- data/docs/assets/{assets.md.D3wunzLx.js → assets.md.7C3HWkga.js} +3 -3
- data/docs/assets/{assets.md.D3wunzLx.lean.js → assets.md.7C3HWkga.lean.js} +1 -1
- data/docs/assets/{brut-js.md.o2DAO2s2.js → brut-js.md.B4GYxQVw.js} +1 -1
- data/docs/assets/{brut-js.md.o2DAO2s2.lean.js → brut-js.md.B4GYxQVw.lean.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.BqRrkR00.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.Dpot_2H4.js → VPLocalSearchBox.DL6bnqee.js} +1 -1
- data/docs/assets/chunks/{theme.N2SNVLgU.js → theme.BXdlf6e8.js} +2 -2
- data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
- data/docs/assets/components.md.Pg_Lo35G.js +96 -0
- data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.Pg_Lo35G.lean.js} +1 -1
- data/docs/assets/{configuration.md.LG-zIBww.js → configuration.md.BfeGnEci.js} +3 -3
- data/docs/assets/{css.md.DJgj2clw.js → css.md.CltvJqAa.js} +3 -3
- data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js} +3 -3
- data/docs/assets/{database-access.md.C7l-Vuvb.js → database-access.md.gnluu54N.js} +1 -1
- data/docs/assets/{database-schema.md.BUjR0VS1.js → database-schema.md.CSYk6E6v.js} +6 -6
- data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js} +1 -1
- data/docs/assets/dev-environment.md.Dy6EldaM.js +16 -0
- data/docs/assets/dev-environment.md.Dy6EldaM.lean.js +1 -0
- data/docs/assets/dir-structure.md.CWir1pic.js +46 -0
- data/docs/assets/dir-structure.md.CWir1pic.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DOkAuXlt.js +1 -0
- data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +1 -0
- data/docs/assets/{end-to-end-tests.md.yfQHC0b5.js → end-to-end-tests.md.DzqRpZ43.js} +5 -3
- data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +1 -0
- data/docs/assets/features.md.DPFXsy0z.js +154 -0
- data/docs/assets/features.md.DPFXsy0z.lean.js +1 -0
- data/docs/assets/flash-and-session.md.nPvUpnUx.js +79 -0
- data/docs/assets/{flash-and-session.md.BXY8RvT0.lean.js → flash-and-session.md.nPvUpnUx.lean.js} +1 -1
- data/docs/assets/form-constraints.md.x5tNpTTI.js +90 -0
- data/docs/assets/form-constraints.md.x5tNpTTI.lean.js +1 -0
- data/docs/assets/forms.md.BQZlCwvi.js +64 -0
- data/docs/assets/forms.md.BQZlCwvi.lean.js +1 -0
- data/docs/assets/{getting-started.md.Dj0qtZI2.js → getting-started.md.BcXnNuD6.js} +5 -5
- data/docs/assets/{getting-started.md.Dj0qtZI2.lean.js → getting-started.md.BcXnNuD6.lean.js} +1 -1
- data/docs/assets/handlers.md.Chyri6KA.js +54 -0
- data/docs/assets/handlers.md.Chyri6KA.lean.js +1 -0
- data/docs/assets/{hooks.md.C4-moMny.js → hooks.md.Jmb5VOLA.js} +4 -4
- data/docs/assets/{hooks.md.C4-moMny.lean.js → hooks.md.Jmb5VOLA.lean.js} +1 -1
- data/docs/assets/{i18n.md.Do9i1qWl.js → i18n.md.xQhiGo1G.js} +2 -2
- data/docs/assets/{i18n.md.Do9i1qWl.lean.js → i18n.md.xQhiGo1G.lean.js} +1 -1
- data/docs/assets/index.md.Bn9e0sRJ.js +1 -0
- data/docs/assets/index.md.Bn9e0sRJ.lean.js +1 -0
- data/docs/assets/{instrumentation.md.a9Pjps4P.js → instrumentation.md.BgcaGVYH.js} +2 -2
- data/docs/assets/{instrumentation.md.a9Pjps4P.lean.js → instrumentation.md.BgcaGVYH.lean.js} +1 -1
- data/docs/assets/{javascript.md.GWbhRS51.js → javascript.md.DzrMxUmI.js} +7 -7
- data/docs/assets/{javascript.md.GWbhRS51.lean.js → javascript.md.DzrMxUmI.lean.js} +1 -1
- data/docs/assets/keyword-injection.md.95Zgh2eN.js +21 -0
- data/docs/assets/{keyword-injection.md.Dt2tKREs.lean.js → keyword-injection.md.95Zgh2eN.lean.js} +1 -1
- data/docs/assets/{layouts.md.cPnh3NId.js → layouts.md.CJGDFY-m.js} +2 -15
- data/docs/assets/layouts.md.CJGDFY-m.lean.js +1 -0
- data/docs/assets/{lsp.md.Bsu-f6VU.js → lsp.md.Dn1rIiW0.js} +1 -1
- data/docs/assets/{lsp.md.Bsu-f6VU.lean.js → lsp.md.Dn1rIiW0.lean.js} +1 -1
- data/docs/assets/overview.md.iMnwLO4x.js +1 -0
- data/docs/assets/overview.md.iMnwLO4x.lean.js +1 -0
- data/docs/assets/pages.md.B7Hc-i6H.js +45 -0
- data/docs/assets/pages.md.B7Hc-i6H.lean.js +1 -0
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.js +22 -0
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +1 -0
- data/docs/assets/recipes_authentication.md.Dzvi_g69.js +156 -0
- data/docs/assets/recipes_authentication.md.Dzvi_g69.lean.js +1 -0
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +15 -0
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +1 -0
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.js +26 -0
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +1 -0
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.js +74 -0
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +1 -0
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.js +101 -0
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +1 -0
- data/docs/assets/roadmap.md.C6PRi0DX.js +1 -0
- data/docs/assets/roadmap.md.C6PRi0DX.lean.js +1 -0
- data/docs/assets/routes.md.B8kfUPHU.js +21 -0
- data/docs/assets/{routes.md.BMM7peut.lean.js → routes.md.B8kfUPHU.lean.js} +1 -1
- data/docs/assets/{security.md.C668yXCi.js → security.md.C0G_AZR-.js} +1 -1
- data/docs/assets/{security.md.C668yXCi.lean.js → security.md.C0G_AZR-.lean.js} +1 -1
- data/docs/assets/space-time-continuum.md.xl44xDos.js +1 -0
- data/docs/assets/{space-time-continuum.md.KPUIKysQ.lean.js → space-time-continuum.md.xl44xDos.lean.js} +1 -1
- data/docs/assets/{style.B2o1L9eN.css → style.B1z60PPQ.css} +1 -1
- data/docs/assets/tutorial.md.BYXj4cOu.js +1 -0
- data/docs/assets/tutorial.md.BYXj4cOu.lean.js +1 -0
- data/docs/assets/why.md.C-hk5xgJ.js +1 -0
- data/docs/assets/why.md.C-hk5xgJ.lean.js +1 -0
- data/docs/assets.html +12 -7
- data/docs/brut-js/api/AjaxSubmit.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +1 -1
- data/docs/brut-js/api/Form.js.html +1 -1
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +12 -7
- data/docs/business-logic.html +10 -5
- data/docs/cli.html +26 -26
- data/docs/components.html +61 -64
- data/docs/configuration.html +13 -8
- data/docs/css.html +14 -9
- data/docs/custom-element-tests.html +14 -9
- data/docs/database-access.html +12 -7
- data/docs/database-schema.html +15 -10
- data/docs/deployment.html +10 -5
- data/docs/dev-environment.html +17 -7
- data/docs/dir-structure.html +74 -0
- data/docs/doc-conventions.html +11 -6
- data/docs/end-to-end-tests.html +15 -8
- data/docs/favicon.ico +0 -0
- data/docs/features.html +182 -0
- data/docs/flash-and-session.html +73 -82
- data/docs/form-constraints.html +118 -0
- data/docs/forms.html +57 -367
- data/docs/getting-started.html +15 -10
- data/docs/handlers.html +51 -61
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +14 -9
- data/docs/i18n.html +12 -7
- data/docs/index.html +11 -6
- data/docs/instrumentation.html +12 -7
- data/docs/javascript.html +17 -12
- data/docs/jobs.html +10 -5
- data/docs/keyword-injection.html +22 -21
- data/docs/layouts.html +12 -20
- data/docs/lsp.html +11 -6
- data/docs/markdown-examples.html +10 -5
- data/docs/middleware.html +10 -5
- data/docs/overview.html +11 -138
- data/docs/pages.html +49 -121
- data/docs/recipes/alternate-layouts.html +50 -0
- data/docs/recipes/authentication.html +166 -6
- data/docs/recipes/blank-layouts.html +43 -0
- data/docs/recipes/custom-flash.html +54 -0
- data/docs/recipes/indexed-forms.html +102 -0
- data/docs/recipes/text-field-component.html +129 -0
- data/docs/roadmap.html +29 -0
- data/docs/routes.html +16 -19
- data/docs/security.html +11 -6
- data/docs/seed-data.html +10 -5
- data/docs/space-time-continuum.html +11 -6
- data/docs/tutorial.html +11 -6
- data/docs/unit-tests.html +10 -5
- data/docs/why.html +29 -0
- data/lib/brut/cli/apps/test.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +2 -2
- data/lib/brut/front_end/form.rb +8 -8
- data/lib/brut/front_end/forms/radio_button_group_input.rb +8 -1
- data/lib/brut/front_end/forms/select_input.rb +8 -1
- data/lib/brut/junk_drawer.rb +48 -9
- data/lib/brut/version.rb +1 -1
- data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +54 -0
- data/specs/brut/front_end/forms/select_input.spec.rb +54 -0
- data/specs/brut/junk_drawer.spec.rb +75 -0
- metadata +129 -82
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/not-released.md +0 -5
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/docs/assets/LogoStop.X8x-4riz.png +0 -0
- data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.CeRAdP1K.js +0 -1
- data/docs/assets/components.md.CRUMdRoN.js +0 -104
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
- data/docs/assets/dev-environment.md.GZv6xvi9.lean.js +0 -1
- data/docs/assets/doc-conventions.md.-kN3Xo5C.js +0 -1
- data/docs/assets/doc-conventions.md.-kN3Xo5C.lean.js +0 -1
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +0 -1
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +0 -93
- data/docs/assets/forms.md.B-koVgyw.js +0 -379
- data/docs/assets/forms.md.B-koVgyw.lean.js +0 -1
- data/docs/assets/handlers.md.089DVD3v.js +0 -69
- data/docs/assets/handlers.md.089DVD3v.lean.js +0 -1
- data/docs/assets/index.md.CuBB-BdM.js +0 -1
- data/docs/assets/index.md.CuBB-BdM.lean.js +0 -1
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +0 -25
- data/docs/assets/layouts.md.cPnh3NId.lean.js +0 -1
- data/docs/assets/not-released.md.BBy28McC.js +0 -1
- data/docs/assets/not-released.md.BBy28McC.lean.js +0 -1
- data/docs/assets/overview.md.DVKRM8zl.js +0 -133
- data/docs/assets/overview.md.DVKRM8zl.lean.js +0 -1
- data/docs/assets/pages.md.BE3kfOc5.js +0 -122
- data/docs/assets/pages.md.BE3kfOc5.lean.js +0 -1
- data/docs/assets/recipes_authentication.md.CAsXf7hk.js +0 -1
- data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +0 -1
- data/docs/assets/routes.md.BMM7peut.js +0 -29
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +0 -1
- data/docs/assets/tutorial.md.BnoGjrdK.js +0 -1
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +0 -1
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/not-released.html +0 -24
- /data/docs/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
- /data/docs/assets/{configuration.md.LG-zIBww.lean.js → configuration.md.BfeGnEci.lean.js} +0 -0
- /data/docs/assets/{css.md.DJgj2clw.lean.js → css.md.CltvJqAa.lean.js} +0 -0
- /data/docs/assets/{custom-element-tests.md.BrYJQEl3.lean.js → custom-element-tests.md.B_rbta32.lean.js} +0 -0
- /data/docs/assets/{database-access.md.C7l-Vuvb.lean.js → database-access.md.gnluu54N.lean.js} +0 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
# Directory Structure
|
2
|
+
|
3
|
+
```
|
4
|
+
.
|
5
|
+
├── app
|
6
|
+
│ ├── config
|
7
|
+
│ │ └── i18n
|
8
|
+
│ │ └── en
|
9
|
+
│ ├── public
|
10
|
+
│ │ ├── css
|
11
|
+
│ │ ├── js
|
12
|
+
│ │ └── static
|
13
|
+
│ │ └── images
|
14
|
+
│ └── src
|
15
|
+
│ ├── back_end
|
16
|
+
│ │ ├── data_models
|
17
|
+
│ │ ├── db
|
18
|
+
│ │ ├── migrations
|
19
|
+
│ │ └── seed
|
20
|
+
│ ├── cli
|
21
|
+
│ └── front_end
|
22
|
+
│ ├── components
|
23
|
+
│ ├── css
|
24
|
+
│ ├── fonts
|
25
|
+
│ ├── forms
|
26
|
+
│ ├── handlers
|
27
|
+
│ ├── images
|
28
|
+
│ ├── js
|
29
|
+
│ ├── layouts
|
30
|
+
│ ├── pages
|
31
|
+
│ ├── route_hooks
|
32
|
+
│ ├── support
|
33
|
+
│ └── svgs
|
34
|
+
├── bin
|
35
|
+
├── deploy
|
36
|
+
├── dx
|
37
|
+
└── specs
|
38
|
+
├── back_end
|
39
|
+
│ ├── data_models
|
40
|
+
│ └── db
|
41
|
+
├── e2e
|
42
|
+
├── factories
|
43
|
+
│ └── db
|
44
|
+
└── front_end
|
45
|
+
├── components
|
46
|
+
├── handlers
|
47
|
+
├── js
|
48
|
+
├── pages
|
49
|
+
└── support
|
50
|
+
```
|
51
|
+
|
52
|
+
## Top Level
|
53
|
+
|
54
|
+
| Directory | Purpose |
|
55
|
+
|-----------|---------|
|
56
|
+
| `app/` | Contains all configuration and source code specific to your app |
|
57
|
+
| `bin/` | Contains tasks and other CLIs to do development of your app, such as `bin/test` |
|
58
|
+
| `dx/` | Contains scripts to manage your development environment |
|
59
|
+
| `specs/` | Contains all tests |
|
60
|
+
|
61
|
+
## Inside `app`/
|
62
|
+
|
63
|
+
| Directory | Purpose |
|
64
|
+
|-----------|---------|
|
65
|
+
| `bootstrap.rb` | A ruby file that sets up your app and ensures everything is `require`d in the right way. |
|
66
|
+
| `config/` | 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. |
|
67
|
+
| `public/` | Root of public assets served by the app. |
|
68
|
+
| `src/` | All source code for your app |
|
69
|
+
|
70
|
+
Inside `app/src`
|
71
|
+
|
72
|
+
| Directory | Purpose |
|
73
|
+
|-----------|---------|
|
74
|
+
| `app.rb` | The core of your app, mostly configuration, such as routes, hooks, middleware, etc. |
|
75
|
+
| `back_end/` | Back end classes for your app including database schema, DB models, seed data, and your domain logic |
|
76
|
+
| `cli/` | Any CLIs or tasks for your app |
|
77
|
+
| `front_end/` | The front-end for your app, including pages, components, forms, handlers, JavaScript, and assets |
|
78
|
+
|
79
|
+
Inside `app/src/back_end`
|
80
|
+
|
81
|
+
| Directory | Purpose |
|
82
|
+
|-----------|---------|
|
83
|
+
| `data_models/app_data_model.rb` | Base class for all DB model classes |
|
84
|
+
| `data_models/db` | DB model classes |
|
85
|
+
| `data_models/db.rb` | Namespace module for DB model classes |
|
86
|
+
| `data_models/migrations` | Database schema migrations |
|
87
|
+
| `data_models/seed` | Seed data used for local development |
|
88
|
+
|
89
|
+
Inside `app/src/front_end`
|
90
|
+
|
91
|
+
|Directory | Purpose |
|
92
|
+
|----------------|---------|
|
93
|
+
| `components/` | Component classes |
|
94
|
+
| `css/` | CSS, managed by esbuild and `bin/build-assets` |
|
95
|
+
| `fonts/` | Custom fonts, managed by esbuild and `bin/build-assets` |
|
96
|
+
| `forms/` | Form classes |
|
97
|
+
| `handlers/` | Handler classes |
|
98
|
+
| `images/` | Images, copied to `app/public` by `bin/build-assets` |
|
99
|
+
| `js/` | JavaScript, managed by esbuild and `bin/build-assets` |
|
100
|
+
| `layouts/` | Layout classes |
|
101
|
+
| `middlewares/` | Rack Middleware, if any |
|
102
|
+
| `pages/` | Page classes |
|
103
|
+
| `route_hooks/` | Route hooks, if any |
|
104
|
+
| `support/` | General support classes/junk drawer. |
|
105
|
+
| `svgs/` | SVGs you want to render inline |
|
106
|
+
|
107
|
+
## Inside `specs/`
|
108
|
+
|
109
|
+
`specs/` is intended to mirror `app/src`, but has a few extra directories:
|
110
|
+
|
111
|
+
|
112
|
+
|Directory | Purpose |
|
113
|
+
|----------------|---------|
|
114
|
+
| `specs/back_end` | tests for all back-end code, organized the same as `app/src/back_end` |
|
115
|
+
| `specs/back_end/data_models/db` | tests for all DB classes, if needed |
|
116
|
+
| `specs/e2e` | End-to-end tests, organized however you like |
|
117
|
+
| `specs/factories` | Root of all factories for FactoryBot. You can create subdirectories here for non-DB classes you may want to be able to create |
|
118
|
+
| `specs/factories/db` | Factories to create DB records |
|
119
|
+
| `specs/front_end` | tests for all front-end code, organized the same as `app/src/front_end` |
|
120
|
+
| `specs/js`| *JavaScript* code to test any autonomous custom elements you have created |
|
@@ -2,32 +2,34 @@
|
|
2
2
|
|
3
3
|
## Terminology
|
4
4
|
|
5
|
-
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 "CSS variables", rather the term is "custom properties".
|
5
|
+
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 "CSS variables", rather the term is "custom properties".
|
6
6
|
|
7
|
-
|
7
|
+
Here are some common exampels:
|
8
8
|
|
9
|
-
Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor. One exception is Phlex, which uses the term *render* to mean "generate HTML to an internal buffer that will be delivered to the client later". Thus, you will need to call Phlex's `render` method from time to time, even though it is not rendering HTML but helping to generate it.
|
10
9
|
|
11
|
-
|
10
|
+
- HTML entities are **elements** or **tags**
|
11
|
+
- HTML elements have **attributes**.
|
12
|
+
- Forms don't have validations, they have **constraints** which are **violated** by invalid data.
|
13
|
+
- Ruby classes don't have constructors, they have **initializers**.
|
14
|
+
- Invoking behavior on a Ruby object is **calling a method**, not sending a message.
|
15
|
+
- Despite being in `specs/`, the files in there are **tests**, not specifications or "specs".
|
16
|
+
- Tests that use a browser are **end to end** or **e2e** tests.
|
17
|
+
- HTML is not rendered, but **generated**. The browser renders the HTML sent to it by the server, along with the CSS.
|
18
|
+
- Your app or site doesn't have users, it has **visitors**.
|
12
19
|
|
13
20
|
## Structure of These Documents
|
14
21
|
|
15
22
|
Each page here documents on aspect of Brut, called a *module*, and these pages are organized along four sections:
|
16
23
|
|
17
|
-
* **Overview** -
|
18
|
-
|
19
|
-
* **
|
20
|
-
* **
|
21
|
-
|
22
|
-
understand the intention of the authors.
|
23
|
-
* **Technical Notes** - where appropriate, technical details about how or why the module works the way it does
|
24
|
-
are provided. This section should be marked with a date to allow you to understand the recency of the
|
25
|
-
information. It may not always be up to date, but this can help further clarify what is happening under the
|
26
|
-
covers and why.
|
24
|
+
* **Overview** - What the module does, how it works, and a brief example.
|
25
|
+
* **Testing** - How to test the code you write in this module.
|
26
|
+
* **Recommended Practices** - Opinions from the creators about how best to think about the code in this module.
|
27
|
+
* **Technical Notes** - details about the technical implementations that may be
|
28
|
+
useful as context.
|
27
29
|
|
28
30
|
## Names of the Library and Associated Modules
|
29
31
|
|
30
|
-
This framework is called "Brut" though may be called "BrutRB". It lives at `brutrb.com`.
|
32
|
+
This framework is called "Brut" though may be called "BrutRB" or "brut-rp". It lives at `brutrb.com`. Never use "brutRB", "brut_rb", etc.
|
31
33
|
|
32
34
|
The JavaScript library is called "BrutJS", but is `brut-js` in code or the filesystem. "Brut-JS" is wrong, as is `brut_js`.
|
33
35
|
|
data/brutrb.com/dx
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
../dx
|
@@ -55,7 +55,7 @@ RSpec.describe "logging into the website" do
|
|
55
55
|
end
|
56
56
|
```
|
57
57
|
|
58
|
-
`playwright-ruby-client` provides excellent documentation on how it has
|
58
|
+
`playwright-ruby-client` provides excellent documentation on how it has adapted Playwright's API for use
|
59
59
|
in Ruby.
|
60
60
|
|
61
61
|
### Test Setup
|
@@ -70,9 +70,15 @@ jobs, but rather assert the effects those jobs will have. Redis is flushed betwe
|
|
70
70
|
|
71
71
|
Inside your test, `t` is available to produce translations. You can also access all your page and handler classes, so you can (and should) use `.routing`, e.g. `DashboardPage.routing`, to generate or access routes for your app.
|
72
72
|
|
73
|
-
You can set `e2e_timeout` on any test to override the default amount of time Playwright will wait for a
|
73
|
+
You can set the `e2e_timeout` metadata on any test to override the default amount of time Playwright will wait for a
|
74
74
|
locator to locate an element. The default is 5 seconds.
|
75
|
-
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
RSpec.describe "Test for login", e2e_timeout: 10 do
|
78
|
+
# ...
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
76
82
|
You can also configure behavior with environment variables:
|
77
83
|
|
78
84
|
| Variable | Default | Purpose |
|
@@ -122,12 +128,9 @@ The main Playwright documentation encourages you to locate elements by "accessib
|
|
122
128
|
indirect ways of finding elements. In practice, this is error prone and tedious. Determining the
|
123
129
|
accessible name of an element is not always easy.
|
124
130
|
|
125
|
-
We recommend you assess your app's
|
126
|
-
end-to-end tests. Instead, locate elements with CSS selectors—this is what you'd use to debug your app so
|
127
|
-
it makes sense as a testing technique.
|
131
|
+
We recommend you assess your app'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'd use to debug your app so it makes sense as a testing technique.
|
128
132
|
|
129
|
-
Insulating your end-to-end tests from markup changes does not produce significant
|
130
|
-
tests more difficult to write.
|
133
|
+
Insulating your end-to-end tests from markup changes does not produce significant savings and can make tests more difficult to write.
|
131
134
|
|
132
135
|
### Testing Must Inform your HTML
|
133
136
|
|
@@ -170,5 +173,4 @@ make sure it's still needed.
|
|
170
173
|
|
171
174
|
_Last Updated June 13, 2025_
|
172
175
|
|
173
|
-
The test server is run
|
174
|
-
running for an e2e test.
|
176
|
+
The test server is run via `bin/test-server`.
|
@@ -0,0 +1,373 @@
|
|
1
|
+
# Quick Tour of Brut's Features
|
2
|
+
|
3
|
+
## Pages
|
4
|
+
|
5
|
+
A [*Page*](/pages) models, well, a web page. It's a class that holds all the data
|
6
|
+
necessary to generate its HTML as well as a method called `page_template`, which
|
7
|
+
generates the HTML via Phlex.
|
8
|
+
|
9
|
+
A page's routing is convention-based and starts with a URL:
|
10
|
+
|
11
|
+
```ruby{6}
|
12
|
+
class App < Brut::Framework::App
|
13
|
+
def id = "my-app"
|
14
|
+
def organization = "my-org"
|
15
|
+
|
16
|
+
routes do
|
17
|
+
page "/dashboard"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
This URL means our page class is expected in `DashboardPage`.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class DashboardPage < AppPage
|
26
|
+
def initialize
|
27
|
+
@now = Time.now
|
28
|
+
end
|
29
|
+
|
30
|
+
def page_template
|
31
|
+
main do
|
32
|
+
h1 { "Hello!" }
|
33
|
+
h2 do
|
34
|
+
plain("It's ")
|
35
|
+
time(datetime: l(@now, format: iso_8601)) do
|
36
|
+
l(@now, format: date)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
This would all produce HTML like so, depending on the value of
|
45
|
+
|
46
|
+
```html [/dashboard]
|
47
|
+
<main>
|
48
|
+
<h1>Hello!</h1>
|
49
|
+
<h2>It's
|
50
|
+
<time datetime="2025-02-17">
|
51
|
+
Monday, Feb 17
|
52
|
+
</time>
|
53
|
+
</h2>
|
54
|
+
</main>
|
55
|
+
```
|
56
|
+
|
57
|
+
Note that the actual HTML delivered would include the code for a layout.
|
58
|
+
|
59
|
+
## Layouts
|
60
|
+
|
61
|
+
Brut includes the concept of [layouts](/layouts), and they work similar to Rails.
|
62
|
+
Layouts are classes, however, and implement the Phlex-standard `view_template`
|
63
|
+
method:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
class DefaultLayout < Brut::FrontEnd::Layout
|
67
|
+
def initialize(page_name:)
|
68
|
+
@page_name = page_name
|
69
|
+
end
|
70
|
+
|
71
|
+
def view_template
|
72
|
+
doctype
|
73
|
+
html(lang: "en") do
|
74
|
+
head do
|
75
|
+
meta(charset: "utf-8")
|
76
|
+
link(rel: "preload", as: "style", href: asset_path("/css/styles.css"))
|
77
|
+
link(rel: "stylesheet", href: asset_path("/css/styles.css"))
|
78
|
+
script(defer: true, src: asset_path("/js/app.js"))
|
79
|
+
title { app_name }
|
80
|
+
end
|
81
|
+
body do
|
82
|
+
yield
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
This produces this HTML:
|
90
|
+
|
91
|
+
```html
|
92
|
+
<!DOCTYPE html>
|
93
|
+
<html>
|
94
|
+
<head>
|
95
|
+
<meta charset="utf-8">
|
96
|
+
<link rel="preload" as="style" href="/css/styles-«HASH».css">
|
97
|
+
<link rel="stylesheet" href="/css/styles-«HASH».css">
|
98
|
+
<script defer src="/js/app-«HASH».js">
|
99
|
+
<title>My Awesome App</title>
|
100
|
+
</head>
|
101
|
+
<body>
|
102
|
+
<-- page HTML here -->
|
103
|
+
</body>
|
104
|
+
</html>
|
105
|
+
```
|
106
|
+
|
107
|
+
|
108
|
+
## Components
|
109
|
+
|
110
|
+
*Components* are a way to manage the complexity of HTML generation. The are Phlex components, meaning they are a class that implements `view_template`.
|
111
|
+
|
112
|
+
Here's an example of a flash message component:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
# components/flash_component.rb
|
116
|
+
class FlashComponent < AppComponent
|
117
|
+
def initialize(flash:)
|
118
|
+
if flash.notice?
|
119
|
+
@message_key = flash.notice
|
120
|
+
@role = :info
|
121
|
+
elsif flash.alert?
|
122
|
+
@message_key = flash.alert
|
123
|
+
@role = :alert
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def any_message? = !@message_key.nil?
|
128
|
+
|
129
|
+
def view_template
|
130
|
+
if any_message?
|
131
|
+
div(role: @role) do
|
132
|
+
t([ :flash, @message_key ])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
You can then use this in any other view using `render`, provided by Phlex.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
def page_template
|
143
|
+
header do
|
144
|
+
render FlashComponent.new(flash:)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
## Forms
|
150
|
+
|
151
|
+
[*Forms*](/forms) are a major concept like pages, since they are the way a browser
|
152
|
+
submits data to the server.
|
153
|
+
|
154
|
+
In Brut, a form does three things:
|
155
|
+
|
156
|
+
* Describes the data in the `<form>` tag
|
157
|
+
* Implies a route where its data is submitted via HTTP POST
|
158
|
+
* Provides access to the submitted data (via an object with methods, not a Hash of Whatever)
|
159
|
+
|
160
|
+
Like `page`, `form` declares a form's route:
|
161
|
+
|
162
|
+
```ruby{2}
|
163
|
+
routes do
|
164
|
+
form "/login"
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
Brut is convention-based, so it will expect a class named `LoginForm` to exist. It
|
169
|
+
will also expect `LoginHandler` to exist, which is a class that will receive the
|
170
|
+
form submission and process it. More on handlers below.
|
171
|
+
|
172
|
+
`LoginForm` uses class methods to declare its inputs. These class methods mirror
|
173
|
+
the various form element tags in HTML (`input`, `select`, etc.), and the methods
|
174
|
+
attributes allow you to declare names and client-side constraints:
|
175
|
+
|
176
|
+
```ruby [forms/login_form.rb]
|
177
|
+
class LoginForm < AppForm
|
178
|
+
input :email
|
179
|
+
input :password, minlength: 8
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
An instance of this class can be used to create HTML:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
def view_template
|
187
|
+
FormTag(for: @form) do
|
188
|
+
Inputs::InputTag(form: @form, input_name: :email)
|
189
|
+
Inputs::InputTag(form: @form, input_name: :password)
|
190
|
+
button { "Login" }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
> [!NOTE]
|
196
|
+
> We'll explain what `FormTag` and `Inputs::InputTag` are in the [forms
|
197
|
+
> section](/forms)
|
198
|
+
|
199
|
+
This generates this HTML:
|
200
|
+
|
201
|
+
```html
|
202
|
+
<form action="/login" method="POST">
|
203
|
+
<input type="email" name="email" required>
|
204
|
+
<input type="password" name="password" required minlength="8">
|
205
|
+
<button>Login</button>
|
206
|
+
</form>
|
207
|
+
```
|
208
|
+
|
209
|
+
When the form is submitted, an instance of `LoginForm` is created and made available
|
210
|
+
to `LoginHandler`
|
211
|
+
|
212
|
+
## Handlers
|
213
|
+
|
214
|
+
A handler is like a controller in Rails, except it only has one method: `handle`.
|
215
|
+
Unlike a Rails controller, a handler class is given its arguments explicitly, and
|
216
|
+
`handle`'s return value dictates what will happen next.
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
class LoginHandler < AppHandler
|
220
|
+
def initialize(form:)
|
221
|
+
@form = form
|
222
|
+
end
|
223
|
+
def handle
|
224
|
+
if @form.email == "secret@example.com" &&
|
225
|
+
@form.password = "sup3rs3cret!"
|
226
|
+
redirect_to(DashboardPage)
|
227
|
+
else
|
228
|
+
form.server_side_constraint_violation(
|
229
|
+
input_name: :email,
|
230
|
+
key: :no_such_user
|
231
|
+
)
|
232
|
+
LoginPage.new(form:)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
```
|
237
|
+
|
238
|
+
Note that we access the form's values as methods, not by digging into a Hash of
|
239
|
+
Whatever. Also note that returning an instance of a page will generate that page's
|
240
|
+
HTML, much like Rails' `render :edit` might. Lastly, `redirect_to` is a
|
241
|
+
convenience to generate a URL to `DashboardPage`, and ultimately causes `handle` to
|
242
|
+
return a `URI`, which Brut interprets as a redirect.
|
243
|
+
|
244
|
+
|
245
|
+
## JavaScript
|
246
|
+
|
247
|
+
Brut doesn't include a front-end framework, however you can certainly use one. All
|
248
|
+
JavaScript is bundled into a single bundle by [esbuild](https://esbuild.github.io/).
|
249
|
+
|
250
|
+
Brut includes [BrutJS](/brut-js), which is a collection of autonomous custom
|
251
|
+
elements AKA Web Components that provide convenient features like autosubmit, form
|
252
|
+
submission confirmation and more:
|
253
|
+
|
254
|
+
```ruby{5,7}
|
255
|
+
def view_template
|
256
|
+
FormTag(for: @form) do
|
257
|
+
Inputs::InputTag(form: @form, input_name: :email)
|
258
|
+
Inputs::InputTag(form: @form, input_name: :password)
|
259
|
+
brut_confirm_submit(message: "Really login? In this economy?!") do
|
260
|
+
button { "Login" }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
When "Login" is pressed, `window.confirm` will ask if the visitor wants to proceed.
|
267
|
+
This custom element can also use a `<dialog>` that you style, and works even better
|
268
|
+
if that `<dialog>` makes use of `<brut-confirmation-dialog>`.
|
269
|
+
|
270
|
+
## CSS
|
271
|
+
|
272
|
+
Brut includes [BrutCSS](/css#using-brut-css), which is a lightweight utility-based
|
273
|
+
CSS library to let you get started quickly. It's *not* TailwindCSS, nor will it ever
|
274
|
+
be.
|
275
|
+
|
276
|
+
You can replace it with whatever you like easily enough.
|
277
|
+
|
278
|
+
|
279
|
+
## Database Schema
|
280
|
+
|
281
|
+
Brut provides access to an SQL database via Sequel. Brut uses Sequel's database schema management, however it is enhanced to
|
282
|
+
encourage good practices by default.
|
283
|
+
|
284
|
+
> [!NOTE]
|
285
|
+
> Brut currently *only supports* PostgreSQL. It may support all RDBMSes that Sequel supports, but as of now,
|
286
|
+
> it's just Postgres.
|
287
|
+
|
288
|
+
Consider a `households` table that relates to an `accounts` table.
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
create_table :households,
|
292
|
+
comment: "Family unit managing the data" do
|
293
|
+
column :timezone, :text
|
294
|
+
column :dinner_time_of_day, :text
|
295
|
+
constraint(
|
296
|
+
:time_must_be_time,
|
297
|
+
%{
|
298
|
+
(dinner_time_of_day ~ '^[01][0-9]:[0-5][0-9]$') OR
|
299
|
+
(dinner_time_of_day ~ '^2[0-3]:[0-5][0-9]$')
|
300
|
+
}
|
301
|
+
)
|
302
|
+
end
|
303
|
+
|
304
|
+
create_table :accounts,
|
305
|
+
comment: "People or systems who can access this system",
|
306
|
+
external_id: true do
|
307
|
+
|
308
|
+
column :email, :email_address, unique: true
|
309
|
+
column :deactivated_at, :timestamptz, null: true
|
310
|
+
foreign_key :household_id, :households
|
311
|
+
end
|
312
|
+
```
|
313
|
+
|
314
|
+
This is mostly using [Sequel's built-in migrations API](https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html). But, a few additional behaviors are happening:
|
315
|
+
|
316
|
+
* Columns default to `NOT NULL`
|
317
|
+
* Tables require comments
|
318
|
+
* Foreign keys default to having constraints and indexes
|
319
|
+
|
320
|
+
There are other quality-of-life features of Brut's migration system, all designed to default to a good practice, with a way to do it
|
321
|
+
however you want when needed.
|
322
|
+
|
323
|
+
## Database Access
|
324
|
+
|
325
|
+
Brut uses `Sequel::Model` to access data in your database. To discourage the conflation of "models of database tables" with "models
|
326
|
+
of your application's domain", these classes are in the `DB` namespace. Thus, the class `DB::Household` would be able to access the
|
327
|
+
`households` table defined above. This frees you up to create a `Household` class to model your domain's logic without being coupled
|
328
|
+
to how you store some data in a database.
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
class DB::Account < AppDataModel
|
332
|
+
has_external_id :ac
|
333
|
+
many_to_one :household
|
334
|
+
end
|
335
|
+
|
336
|
+
class DB::Household < AppDataModel
|
337
|
+
one_to_many :accounts
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
341
|
+
## Domain and Business Logic
|
342
|
+
|
343
|
+
Brut uses Zeitwerk for code loading, so any directories you create will be auto-loaded and refreshed during development. This means that you can create a class named `Household` in `app/src/back_end/domain/household.rb` and it would be loaded. Or, you could create `HouseholdService` in `app/src/back_end/services/household_service.rb` if you like.
|
344
|
+
|
345
|
+
> [!TIP]
|
346
|
+
> Providing a generally-useful abstraction for business or domain logic is not usually feasible.
|
347
|
+
> Thus, Brut doesn't provide much beyond Zeitwerk's auto-loading feature. It may provide more
|
348
|
+
> assistance in the future, but for now, Brut's approach is to free you from any prescription
|
349
|
+
> or moral imperative. Manage your domain and business logic how you see fit. You know your domain
|
350
|
+
> and team better than we do.
|
351
|
+
|
352
|
+
## Testing
|
353
|
+
|
354
|
+
Brut provides support for three types of tests:
|
355
|
+
|
356
|
+
* Unit Tests, using RSpec
|
357
|
+
* End-to-end tests, using RSpec and Playwright
|
358
|
+
* Custom Element tests, written in JavaScript, using Mocha
|
359
|
+
|
360
|
+
Since Brut is based on classes, objects, and methods, your unit tests will usually
|
361
|
+
be straightforward, however Brut provides helpers to test your Page and Component
|
362
|
+
HTML using Nokogiri. FactoryBot is included and configured to manage test data.
|
363
|
+
|
364
|
+
## Tasks
|
365
|
+
|
366
|
+
Brut doesn't use Rake tasks. It uses CLI apps powered by Ruby's `OptionParser`. Brut provides bootstrapping classes to make your own CLIs, as well as some light abstractions to make `OptionParser` a little more ergonomic. Brut's dev and production management CLIs are built using this support.
|
367
|
+
|
368
|
+
## Observability
|
369
|
+
|
370
|
+
Brut has built-in support for [OpenTelemetry](https://opentelemetry.io/). Brut includes configuration for the [otel-desktop-viewer](https://github.com/CtrlSpice/otel-desktop-viewer) or a text-based viewer suitable for development. For production, most observability vendors provide OpenTelemetry ingestion any many have free tiers.
|
371
|
+
|
372
|
+
Brut does support logging, however you are encouraged to use OpenTelemetry instead.
|
373
|
+
|