brut 0.0.26 → 0.0.28
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/brutrb.com/.vitepress/config.mjs +2 -0
- data/brutrb.com/ai.md +6 -1
- data/brutrb.com/components.md +1 -1
- data/brutrb.com/dev-environment.md +6 -5
- data/brutrb.com/doc-conventions.md +1 -1
- data/brutrb.com/forms.md +22 -26
- data/brutrb.com/getting-started.md +94 -23
- data/brutrb.com/instrumentation.md +12 -0
- data/brutrb.com/layouts.md +130 -0
- data/brutrb.com/lsp.md +23 -0
- data/docs/404.html +2 -2
- data/docs/ai.html +5 -5
- 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 +38 -13
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +9 -3
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +11 -11
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
- data/docs/api/Brut/CLI/Apps/DB/Status.html +12 -12
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +270 -0
- data/docs/api/Brut/CLI/Apps/DeployBase.html +257 -0
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +585 -0
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +196 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
- 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 +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +12 -10
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +8 -8
- data/docs/api/Brut/CLI/Apps/Test/JS.html +9 -9
- data/docs/api/Brut/CLI/Apps/Test/Run.html +18 -18
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +2 -2
- data/docs/api/Brut/CLI/Command.html +113 -28
- 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 +169 -38
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +68 -19
- 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 +110 -29
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +89 -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 +31 -8
- 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 +36 -26
- data/docs/api/Brut/FrontEnd/Component.html +7 -7
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +37 -29
- 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 +20 -117
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +25 -23
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +73 -380
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +22 -138
- 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 +23 -2
- 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 +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +24 -68
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +4 -4
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +72 -22
- 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 +6 -2
- 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 +1 -1
- 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 +20 -20
- 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 +2 -2
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +142 -0
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +142 -0
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +155 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +55 -25
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +149 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +46 -19
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +149 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +149 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +165 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +158 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +156 -0
- data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +24 -24
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +55 -20
- 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 +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +5 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +36 -28
- 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/SpecSupport/Matchers/BeABug.html +143 -0
- data/docs/api/_index.html +92 -1
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/method_list.html +392 -336
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{ai.md.tZrjP9im.js → ai.md._6HCDL6d.js} +1 -1
- data/docs/assets/ai.md._6HCDL6d.lean.js +1 -0
- data/docs/assets/{app.D_yaTITQ.js → app.BX81XO4N.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.CoYzciVi.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.B2-ZzyTY.js → VPLocalSearchBox.gABXcTWp.js} +1 -1
- data/docs/assets/chunks/{theme.CfGFVRvE.js → theme.DwUXXAL3.js} +2 -2
- data/docs/assets/{components.md.eCttGlN-.js → components.md.CRUMdRoN.js} +1 -1
- data/docs/assets/{configuration.md.BRriU0cL.js → configuration.md.BGHl8oRC.js} +1 -1
- data/docs/assets/{dev-environment.md.BNc8AYiK.js → dev-environment.md.GZv6xvi9.js} +1 -1
- data/docs/assets/doc-conventions.md.-kN3Xo5C.js +1 -0
- data/docs/assets/{doc-conventions.md.DCfRXXi-.lean.js → doc-conventions.md.-kN3Xo5C.lean.js} +1 -1
- data/docs/assets/{forms.md.CBTYQ_Cz.js → forms.md.B-koVgyw.js} +23 -23
- data/docs/assets/{forms.md.CBTYQ_Cz.lean.js → forms.md.B-koVgyw.lean.js} +1 -1
- data/docs/assets/getting-started.md.Ciz82L0m.js +25 -0
- data/docs/assets/getting-started.md.Ciz82L0m.lean.js +1 -0
- data/docs/assets/{instrumentation.md.CL6ax7nT.js → instrumentation.md.a9Pjps4P.js} +2 -2
- data/docs/assets/{instrumentation.md.CL6ax7nT.lean.js → instrumentation.md.a9Pjps4P.lean.js} +1 -1
- data/docs/assets/layouts.md.cPnh3NId.js +51 -0
- data/docs/assets/layouts.md.cPnh3NId.lean.js +1 -0
- data/docs/assets/lsp.md.Bsu-f6VU.js +1 -0
- data/docs/assets/lsp.md.Bsu-f6VU.lean.js +1 -0
- data/docs/assets/{overview.md.CDalkuxV.js → overview.md.C5wlBcR5.js} +3 -3
- data/docs/assets/recipes_authentication.md.CAsXf7hk.js +1 -0
- data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +1 -0
- data/docs/assets.html +4 -4
- 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 +4 -4
- data/docs/business-logic.html +4 -4
- data/docs/cli.html +4 -4
- data/docs/components.html +6 -6
- data/docs/configuration.html +5 -5
- data/docs/css.html +4 -4
- data/docs/custom-element-tests.html +4 -4
- data/docs/database-access.html +4 -4
- data/docs/database-schema.html +4 -4
- data/docs/deployment.html +4 -4
- data/docs/dev-environment.html +5 -5
- data/docs/doc-conventions.html +5 -5
- data/docs/end-to-end-tests.html +4 -4
- data/docs/flash-and-session.html +4 -4
- data/docs/forms.html +27 -27
- data/docs/getting-started.html +29 -6
- data/docs/handlers.html +4 -4
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +4 -4
- data/docs/i18n.html +4 -4
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +5 -5
- data/docs/javascript.html +4 -4
- data/docs/jobs.html +4 -4
- data/docs/keyword-injection.html +4 -4
- data/docs/layouts.html +74 -0
- data/docs/lsp.html +24 -0
- data/docs/markdown-examples.html +4 -4
- data/docs/middleware.html +4 -4
- data/docs/not-released.html +4 -4
- data/docs/overview.html +8 -8
- data/docs/pages.html +5 -5
- data/docs/recipes/authentication.html +24 -0
- data/docs/routes.html +4 -4
- data/docs/security.html +4 -4
- data/docs/seed-data.html +4 -4
- data/docs/space-time-continuum.html +4 -4
- data/docs/tutorial.html +4 -4
- data/docs/unit-tests.html +4 -4
- data/lib/brut/cli/app.rb +7 -2
- data/lib/brut/cli/apps/deploy_base.rb +86 -0
- data/lib/brut/cli/apps/heroku_container_based_deploy.rb +194 -0
- data/lib/brut/cli/apps/test.rb +2 -1
- data/lib/brut/cli/command.rb +7 -13
- data/lib/brut/cli/executor.rb +31 -5
- data/lib/brut/cli/options.rb +4 -0
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/framework/container.rb +25 -7
- data/lib/brut/framework/errors/abstract_method.rb +7 -0
- data/lib/brut/framework/errors.rb +4 -2
- data/lib/brut/front_end/component.rb +33 -9
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +16 -74
- data/lib/brut/front_end/forms/constraint_violation.rb +2 -18
- data/lib/brut/front_end/forms/input.rb +8 -8
- data/lib/brut/front_end/forms/input_declarations.rb +3 -3
- data/lib/brut/front_end/forms/radio_button_group_input.rb +1 -1
- data/lib/brut/front_end/forms/select_input.rb +1 -1
- data/lib/brut/front_end/forms/validity_state.rb +23 -0
- data/lib/brut/front_end/middlewares/reload_app.rb +2 -0
- data/lib/brut/spec_support/handler_support.rb +1 -1
- data/lib/brut/spec_support/matcher.rb +1 -1
- data/lib/brut/spec_support/matchers/be_a_bug.rb +11 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +10 -0
- data/lib/brut/spec_support/matchers/be_routing_for.rb +18 -0
- data/lib/brut/spec_support/matchers/have_constraint_violation.rb +10 -0
- data/lib/brut/spec_support/matchers/have_generated.rb +28 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +10 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +13 -0
- data/lib/brut/spec_support/matchers/have_link_to.rb +15 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +23 -0
- data/lib/brut/spec_support/matchers/have_returned_http_status.rb +20 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +22 -0
- data/lib/brut/spec_support/rspec_setup.rb +42 -2
- data/lib/brut/version.rb +1 -1
- data/lib/sequel/extensions/brut_instrumentation.rb +4 -0
- metadata +51 -24
- data/docs/assets/ai.md.tZrjP9im.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +0 -1
- data/docs/assets/doc-conventions.md.DCfRXXi-.js +0 -1
- data/docs/assets/getting-started.md.Bz2s1Vjb.js +0 -2
- data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +0 -1
- data/lib/brut/spec_support/matchers/have_rendered.rb +0 -14
- /data/docs/assets/{components.md.eCttGlN-.lean.js → components.md.CRUMdRoN.lean.js} +0 -0
- /data/docs/assets/{configuration.md.BRriU0cL.lean.js → configuration.md.BGHl8oRC.lean.js} +0 -0
- /data/docs/assets/{dev-environment.md.BNc8AYiK.lean.js → dev-environment.md.GZv6xvi9.lean.js} +0 -0
- /data/docs/assets/{overview.md.CDalkuxV.lean.js → overview.md.C5wlBcR5.lean.js} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0212844b1dd1d69682b3b4ec4ab2a9917087b3d931916e39d9ccf0239d6a2f9c'
|
4
|
+
data.tar.gz: 871a018ce32f9d1f556eef7950570c8a4dd072736be733e6a7a925e14a8c968c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb49d9e87e53893962c86e2a26ca54f8c82cc9a8fbbeac5e12e1d7aaa4b47cbb13de6ab9429779422a6bc075dd3f4499bf6b18accb7c7ff87943eb32afda0342
|
7
|
+
data.tar.gz: c8f56cb073d99b1b3f304afe2daf3839b6b2b39ebe492bde05f00037ebf448459049560d3b32707450b65e32916366ea766d57598752026d0c025d1db63d7fe7
|
data/Gemfile.lock
CHANGED
@@ -39,6 +39,7 @@ export default defineConfig({
|
|
39
39
|
items: [
|
40
40
|
{ text: "Routes", link: "/routes" },
|
41
41
|
{ text: "Pages", link: "/pages" },
|
42
|
+
{ text: "Layouts", link: "/layouts" },
|
42
43
|
{ text: "Forms", link: "/forms" },
|
43
44
|
{ text: "Handlers and Actions", link: "/handlers" },
|
44
45
|
{ text: "Components", link: "/components" },
|
@@ -89,6 +90,7 @@ export default defineConfig({
|
|
89
90
|
{ text: "Middleware", link: "/middleware" },
|
90
91
|
{ text: "Instrumentation", link: "/instrumentation" },
|
91
92
|
{ text: "Security", link: "/security" },
|
93
|
+
{ text: "LSP Support", link: "/lsp" },
|
92
94
|
],
|
93
95
|
},
|
94
96
|
{
|
data/brutrb.com/ai.md
CHANGED
@@ -58,7 +58,7 @@ The logo is level 4 - ChatGPT created it. I'd love to have a real person make a
|
|
58
58
|
something to get this launched. Please reach out if you want to make a better one + other assets. I'm
|
59
59
|
willing to pay a real person.
|
60
60
|
|
61
|
-
## AI
|
61
|
+
## AI Completions Should Be Viewed with Skepticism
|
62
62
|
|
63
63
|
Due to how LLMs work, there is naturally nothing in any model about Brut. Anything an
|
64
64
|
existing model tells you about Brut is **100% untrustworthy**. We hope to allow LLMs to consume Brut's
|
@@ -66,3 +66,8 @@ code, documentation, and examples, so it can be an additional source of help, bu
|
|
66
66
|
the case.
|
67
67
|
|
68
68
|
**Do not ask an LLM about Brut** until this part of the documentation changes.
|
69
|
+
|
70
|
+
For completion-based AI suggestions, **view them with skepticism**. In my
|
71
|
+
experience, completions from e.g. GitHub CoPilot work OK when replicating a pattern
|
72
|
+
in the file you are editing, but suggestions in a freshly-opened file tend to be
|
73
|
+
entirely imaginary or Rails-based. Beware.
|
data/brutrb.com/components.md
CHANGED
@@ -136,7 +136,7 @@ To use it without having to instantiate it, call `global_component` with the com
|
|
136
136
|
class HomePage < AppPage
|
137
137
|
def page_template
|
138
138
|
header do
|
139
|
-
|
139
|
+
global_component(FlashMessage) # note: render not required
|
140
140
|
end
|
141
141
|
end
|
142
142
|
end
|
@@ -137,16 +137,17 @@ that the versions of these command line apps provided when you set up your app a
|
|
137
137
|
While you are free to set up mise or rbenv or whatever to run all this on your computer, this way of
|
138
138
|
working is currently not supported nor encouraged. For now, Brut will focus on the Docker-based approach.
|
139
139
|
|
140
|
-
|
141
|
-
|
140
|
+
The primary reason is that it's a tightly controlled environment that is almost
|
141
|
+
entirely scriptable, but does not require devs to abandon their preffered editor.
|
142
|
+
Environment manager-based approaches tend to be more fussy and require documentation
|
143
|
+
to ensure they are set up.
|
142
144
|
|
143
|
-
|
145
|
+
Keep in mind a few things when adding your own automation:
|
144
146
|
|
145
147
|
* The *Foundational Core* is bootstrapped in a degenerate environment without reliable tools beyond Bash.
|
146
148
|
This is why it's almost entirely written in Bash, since it's available everywhere and relatively stable.
|
147
149
|
* The *Workspace* **can and should** rely on the languages and third party modules that are part of your
|
148
|
-
app.
|
149
|
-
particular Ruby gem having been installed.
|
150
|
+
app. The only exception is `bin/setup`, since it installs third party modules. As such, it should work entirely based on Ruby and its standard library.
|
150
151
|
|
151
152
|
## Technical Notes
|
152
153
|
|
@@ -6,7 +6,7 @@ Brut attempts to use existing terminology where possible, particularly where tha
|
|
6
6
|
|
7
7
|
When speaking about Ruby, we prefer the term *initializer* over constructor, *parameters* over arguments, and *methods* over messages. We also prefer *tests* over specs, however test files *are* located in `specs/` and named `*.spec.rb` to be consistent with RSpec's nomenclature. We prefer *end-to-end* or *e2e* tests instead of browser tests or request specs.
|
8
8
|
|
9
|
-
Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor.
|
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
10
|
|
11
11
|
Lastly, the documentation tries to talk about the person accessing a website as a "vistor" not a "user". Though the "user" nomenclature is near-ossified in software development, we feel "visitor" is more apt.
|
12
12
|
|
data/brutrb.com/forms.md
CHANGED
@@ -87,7 +87,7 @@ class LoginPage < AppPage
|
|
87
87
|
end
|
88
88
|
```
|
89
89
|
|
90
|
-
Brut can generate the HTML for the needed inputs via `Brut::FrontEnd::Components::Inputs::TextField
|
90
|
+
Brut can generate the HTML for the needed inputs via `Brut::FrontEnd::Components::Inputs::TextField`, which is a very long class name. Hold that thought for now. This method will generate an `<input>` element for you, based on how you've set up the field in your form class. The HTML element will have a value set based on the form, if there is a value.
|
91
91
|
|
92
92
|
```ruby {11,12}
|
93
93
|
# app/src/front_end/pages/login_page.rb
|
@@ -100,8 +100,8 @@ class LoginPage < AppPage
|
|
100
100
|
form_tag(method: :post,
|
101
101
|
action: LoginHandler.routing) do
|
102
102
|
# We promise you don't have to type this every time!
|
103
|
-
Brut::FrontEnd::Components::Inputs::TextField.
|
104
|
-
Brut::FrontEnd::Components::Inputs::TextField.
|
103
|
+
Brut::FrontEnd::Components::Inputs::TextField.new(form: @form, input_name: :email)
|
104
|
+
Brut::FrontEnd::Components::Inputs::TextField.new(form: @form, input_name: :password)
|
105
105
|
button { "Login" }
|
106
106
|
end
|
107
107
|
end
|
@@ -134,7 +134,7 @@ class LoginPage < AppPage
|
|
134
134
|
end
|
135
135
|
```
|
136
136
|
|
137
|
-
This allows you to call `Inputs::TextField
|
137
|
+
This allows you to call `Inputs::TextField` like a method:
|
138
138
|
|
139
139
|
```ruby {12,13}
|
140
140
|
# app/src/front_end/pages/login_page.rb
|
@@ -148,8 +148,8 @@ class LoginPage < AppPage
|
|
148
148
|
def page_template
|
149
149
|
form_tag(method: :post,
|
150
150
|
action: LoginHandler.routing) do
|
151
|
-
Inputs::TextField
|
152
|
-
Inputs::TextField
|
151
|
+
Inputs::TextField(form: @form, input_name: :email)
|
152
|
+
Inputs::TextField(form: @form, input_name: :password)
|
153
153
|
button { "Login" }
|
154
154
|
end
|
155
155
|
end
|
@@ -217,7 +217,7 @@ with that email and password. Let's assume the existence of the class `Authorize
|
|
217
217
|
If that returns `nil`, we want to re-render the `LoginPage`, exposing some sort of constraint violation message
|
218
218
|
so it can be rendered. We also want the form fields to be pre-filled with the values the visitor provided.
|
219
219
|
|
220
|
-
`
|
220
|
+
`Brut::FrontEnd::Components` can handle this, so we need to pass our form object into `LoginPage` instead of allowing `LoginPage` to create an empty one. We can do that by adding a `form:` keyword argument that defaults to `nil`:
|
221
221
|
|
222
222
|
```ruby {3,4}
|
223
223
|
# app/src/front_end/pages/login_page.rb
|
@@ -229,8 +229,8 @@ class LoginPage < AppPage
|
|
229
229
|
def page_template
|
230
230
|
form_tag(method: :post,
|
231
231
|
action: LoginHandler.routing) do
|
232
|
-
Inputs::TextField
|
233
|
-
Inputs::TextField
|
232
|
+
Inputs::TextField(form: @form, input_name: :email)
|
233
|
+
Inputs::TextField(form: @form, input_name: :password)
|
234
234
|
button { "Login" }
|
235
235
|
end
|
236
236
|
end
|
@@ -267,12 +267,11 @@ class LoginHandler < AppHandler
|
|
267
267
|
end
|
268
268
|
```
|
269
269
|
|
270
|
-
When `LoginPage` generates HTML, different HTML is generated, since the form being passed to
|
271
|
-
`for_form_input` contains constraint violations.
|
270
|
+
When `LoginPage` generates HTML, different HTML is generated, since the form being passed to the components contains constraint violations.
|
272
271
|
|
273
272
|
#### Showing Constraint Violations in HTML
|
274
273
|
|
275
|
-
When `Inputs::TextField
|
274
|
+
When `Brut::FrontEnd::Components::Inputs::TextField` is created with an existing form that has constraint violations, different HTML is generated. This is what would be produced by our existing `LoginPage` (again, formatted her for clarity):
|
276
275
|
|
277
276
|
```html {3}
|
278
277
|
<form method="post" action="/login">
|
@@ -335,10 +334,10 @@ def page_template
|
|
335
334
|
form_tag(method: :post,
|
336
335
|
action: LoginHandler.routing) do
|
337
336
|
|
338
|
-
Inputs::TextField
|
337
|
+
Inputs::TextField(form: @form, input_name: :email)
|
339
338
|
ConstraintViolations(form: @form, input_name: :email)
|
340
339
|
|
341
|
-
Inputs::TextField
|
340
|
+
Inputs::TextField(form: @form, input_name: :password)
|
342
341
|
ConstraintViolations(form: @form, input_name: :password)
|
343
342
|
|
344
343
|
button { "Login" }
|
@@ -419,10 +418,10 @@ def page_template
|
|
419
418
|
form_tag(method: :post,
|
420
419
|
action: LoginHandler.routing) do
|
421
420
|
|
422
|
-
Inputs::TextField
|
421
|
+
Inputs::TextField(form: @form, input_name: :email)
|
423
422
|
ConstraintViolations(form: @form, input_name: :email)
|
424
423
|
|
425
|
-
Inputs::TextField
|
424
|
+
Inputs::TextField(form: @form, input_name: :password)
|
426
425
|
ConstraintViolations(form: @form, input_name: :password)
|
427
426
|
|
428
427
|
button { "Login" }
|
@@ -564,7 +563,7 @@ class LoginForm < AppForm
|
|
564
563
|
end
|
565
564
|
```
|
566
565
|
|
567
|
-
Checkboxes can be rendered by `Inputs::TextField
|
566
|
+
Checkboxes can be rendered by a `Brut::FrontEnd::Components::Inputs::TextField`, and their `value` attribute would always be the string `"true"`. If the form's value for the input is the string `"true"`, the checkbox would have the `checked` attribute:
|
568
567
|
|
569
568
|
```html
|
570
569
|
<!-- Form.new(params: { remember: "true" }) -->
|
@@ -578,8 +577,7 @@ Radio buttons are implemented in HTML by `<input type="radio">`, with an expecta
|
|
578
577
|
having the same value for the `name` attribute, but different values for the `value` attributes, one of which may
|
579
578
|
be `checked`.
|
580
579
|
|
581
|
-
Brut implements this via `Brut::FrontEnd::Components::Inputs::RadioButton`,
|
582
|
-
`for_form_input`. To create radio buttons in a form, use `radio_button_group`:
|
580
|
+
Brut implements this via `Brut::FrontEnd::Components::Inputs::RadioButton`, whose initializer behaves like the other form input components. To create radio buttons in a form, use `radio_button_group`:
|
583
581
|
|
584
582
|
```ruby {5}
|
585
583
|
# app/src/front_end/forms/login_form.rb
|
@@ -598,7 +596,7 @@ def view_template
|
|
598
596
|
[ :never, :one_week, :one_month ].each do |remember|
|
599
597
|
label do
|
600
598
|
render(
|
601
|
-
Inputs::RadioButton
|
599
|
+
Inputs::RadioButton(
|
602
600
|
form:,
|
603
601
|
input_name: :remember,
|
604
602
|
value: remember
|
@@ -643,8 +641,7 @@ class LoginForm < AppForm
|
|
643
641
|
end
|
644
642
|
```
|
645
643
|
|
646
|
-
Creating the HTML can be done with `Brut::FrontEnd::Components::Inputs::Select`. It's
|
647
|
-
as well as to allow for a "blank" entry.
|
644
|
+
Creating the HTML can be done with `Brut::FrontEnd::Components::Inputs::Select`. It's initializer is more complex, since it provides a way to show visitor-friendly values instead of the innate `value` for each option, as well as to allow for a "blank" entry.
|
648
645
|
|
649
646
|
Let's suppose we have a class named `LoginRememberOption`. It's a simple wrapper around a value we might store in the database and use to lookup an I18n key.
|
650
647
|
|
@@ -677,7 +674,7 @@ To show these options in a `<select>`, we might do this:
|
|
677
674
|
def view_template
|
678
675
|
form do
|
679
676
|
render(
|
680
|
-
Inputs::Select
|
677
|
+
Inputs::Select(
|
681
678
|
form:,
|
682
679
|
input_name: :remember,
|
683
680
|
options: LoginRememberOption.all,
|
@@ -718,8 +715,7 @@ end
|
|
718
715
|
|
719
716
|
In this case, we need `required: false` or every single field we generate will be required.
|
720
717
|
|
721
|
-
To generate the HTML, use the optional `index:` parameter to
|
722
|
-
`ConstraintViolations`:
|
718
|
+
To generate the HTML, use the optional `index:` parameter to the initializer as well as for `ConstraintViolations`:
|
723
719
|
|
724
720
|
```ruby {11,16}
|
725
721
|
# Inside e.g. app/src/front_end/pages/create_bulk_widget_page.rb
|
@@ -729,7 +725,7 @@ def page_template
|
|
729
725
|
action: BulkWidgetForm.routing) do
|
730
726
|
|
731
727
|
10.times do |i|
|
732
|
-
Inputs::TextField
|
728
|
+
Inputs::TextField(
|
733
729
|
form: @form,
|
734
730
|
input_name: :name,
|
735
731
|
index: i
|
@@ -1,66 +1,137 @@
|
|
1
1
|
# Getting Started
|
2
2
|
|
3
|
-
|
3
|
+
Brut is developed alongside a separate gem called `mkbrut`, which allows you to
|
4
|
+
create a new Brut app. It will set up your dev environment as well.
|
4
5
|
|
5
|
-
##
|
6
|
+
## Get `mkbrut`
|
6
7
|
|
7
|
-
|
8
|
+
The simplest way to use `mkbrut` is to use an existing [Docker image](https://hub.docker.com/repository/docker/thirdtank/mkbrut/general). You don't have to install or configure Ruby:
|
8
9
|
|
9
10
|
```
|
10
|
-
|
11
|
+
docker run \
|
12
|
+
-v "$PWD":"$PWD" \
|
13
|
+
-w "$PWD" \
|
14
|
+
-it \
|
15
|
+
thirdtank/mkbrut \
|
16
|
+
mkbrut my-new-app
|
17
|
+
```
|
18
|
+
|
19
|
+
If you already have Ruby 3.4 installed, you can install `mkbrut` directly:
|
20
|
+
|
21
|
+
```
|
22
|
+
> gem install mkbrut
|
23
|
+
> mkbrut my-new-app
|
11
24
|
```
|
12
25
|
|
13
26
|
## Init Your App
|
14
27
|
|
15
|
-
|
28
|
+
A Brut app just needs a name, which will be used to derive a few more useful values.
|
29
|
+
For now:
|
16
30
|
|
31
|
+
::: code-group
|
32
|
+
|
33
|
+
``` [Docker-based]
|
34
|
+
docker run \
|
35
|
+
-v "$PWD":"$PWD" \
|
36
|
+
-w "$PWD" \
|
37
|
+
-it \
|
38
|
+
thirdtank/mkbrut \
|
39
|
+
mkbrut my-new-app
|
17
40
|
```
|
18
|
-
|
19
|
-
|
41
|
+
|
42
|
+
``` [RubyGems-based]
|
43
|
+
mkbrut my-new-app
|
20
44
|
```
|
21
45
|
|
22
|
-
|
46
|
+
:::
|
47
|
+
|
48
|
+
This will create your new app, along with some demo routes, components, handlers, and tests. If this is your first time using Brut, we recommend you examine these demo components.
|
49
|
+
|
50
|
+
To create your app without the demo components:
|
23
51
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
52
|
+
::: code-group
|
53
|
+
|
54
|
+
``` [Docker-based]
|
55
|
+
docker run \
|
56
|
+
-v "$PWD":"$PWD" \
|
57
|
+
-w "$PWD" \
|
58
|
+
-it \
|
59
|
+
thirdtank/mkbrut \
|
60
|
+
mkbrut my-new-app --no-demo
|
61
|
+
```
|
62
|
+
|
63
|
+
``` [RubyGems-based]
|
64
|
+
mkbrut my-new-app --no-demo
|
65
|
+
```
|
28
66
|
|
29
|
-
::: tip
|
30
|
-
Choose your app's name wisely, however everything else can be easily changed later, so don't stress!
|
31
67
|
:::
|
32
68
|
|
33
|
-
##
|
69
|
+
## Start Your Dev Environment
|
34
70
|
|
35
|
-
Brut includes a dev environment based on Docker.
|
71
|
+
Brut includes a dev environment based on Docker. It uses Docker compose to run a
|
72
|
+
Docker container where your app will run, a Docker container for Postgres, and a
|
73
|
+
Docker container for local observability via OpenTelemetry.
|
36
74
|
|
37
75
|
1. [Install Docker](https://docs.docker.com/get-started/get-docker/)
|
38
|
-
2. Build
|
76
|
+
2. Build the image used to create you app's container:
|
39
77
|
|
40
78
|
```
|
41
79
|
> dx/build
|
42
80
|
```
|
43
|
-
3. Start up the
|
81
|
+
3. Start up all the containers:
|
44
82
|
|
45
83
|
```
|
46
84
|
> dx/start
|
47
85
|
```
|
48
|
-
4.
|
86
|
+
4. Now, "log in" to the container where your app and its tests will run:
|
87
|
+
|
88
|
+
```
|
89
|
+
> dx/exec login
|
90
|
+
```
|
91
|
+
|
92
|
+
5. Set everything up:
|
49
93
|
|
50
94
|
```
|
51
|
-
>
|
95
|
+
inside-container> bin/setup
|
52
96
|
```
|
53
97
|
|
54
|
-
Now, you're ready to go
|
98
|
+
Now, you're ready to go. See [Dev Environemnt](/dev-environment) for details on how
|
99
|
+
this all works.
|
55
100
|
|
56
101
|
## Run the App
|
57
102
|
|
58
103
|
```
|
59
|
-
>
|
104
|
+
inside-container> bin/dev
|
60
105
|
```
|
61
106
|
|
62
107
|
You can now visit your app at `localhost:6502`
|
63
108
|
|
109
|
+
You can make changes and see them when you reload. Open up `app/src/front_end/pages/home_page.rb` *in your editor running on your computer* and change the `h1` to look like so:
|
110
|
+
|
111
|
+
```ruby {6}
|
112
|
+
class HomePage < AppPage
|
113
|
+
def page_template
|
114
|
+
div(class: "flex flex-column items-center justify-center h-80vh") do
|
115
|
+
img(src: "/static/images/icon.png", class: "h-50")
|
116
|
+
h1(class: "ff-sans ma-0 lh-title f-5") do
|
117
|
+
"Welcome to My New App!"
|
118
|
+
end
|
119
|
+
|
120
|
+
# ...
|
121
|
+
```
|
122
|
+
|
123
|
+
When you reload your browser, you'll see your change
|
124
|
+
|
125
|
+
## Run the Tests
|
126
|
+
|
127
|
+
There are a few tests you can run, as well as some checks that you aren't using
|
128
|
+
RubyGems with security vulnerabilities. Run it all now with `bin/ci`:
|
129
|
+
|
130
|
+
```
|
131
|
+
inside-container> bin/dev
|
132
|
+
```
|
133
|
+
|
64
134
|
## Now Build The Rest of Your App 🦉
|
65
135
|
|
66
|
-
You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs.
|
136
|
+
You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs. You might also want to check out the docs for [LSP Support](/lsp).
|
137
|
+
|
@@ -66,6 +66,17 @@ Here is a non-exhaustive list of what Brut automatically instruments:
|
|
66
66
|
* Ignored parameters on all form submissions
|
67
67
|
* How long reloading takes in development
|
68
68
|
* CSP reporting results
|
69
|
+
* SQL Statements
|
70
|
+
|
71
|
+
> [!WARNING]
|
72
|
+
> `Sequel::Extensions::BrutInstrumentation` sets up telemetry for
|
73
|
+
> Sequel, and it does it in a relatively simplistic way. The result
|
74
|
+
> is that *all* SQL statements are part of the telemetry, including
|
75
|
+
> the actual values inserted or used in `WHERE` clauses.
|
76
|
+
> While you should not be putting sensitive data into your database,
|
77
|
+
> be warned that this is happening. There are plans to improve this
|
78
|
+
> to be more flexible and reduce the Schance of sensitive data
|
79
|
+
> being sent in traces.
|
69
80
|
|
70
81
|
### Adding Your Own Instrumentation
|
71
82
|
|
@@ -175,6 +186,7 @@ specific issues.
|
|
175
186
|
|
176
187
|
_Last Updated June 12, 2025_
|
177
188
|
|
189
|
+
|
178
190
|
Brut does not have plans to support non-OTel instrumentation, nor does it have plans to provide hooks to
|
179
191
|
use proprietary formats. This could change of course.
|
180
192
|
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Layouts
|
2
|
+
|
3
|
+
Brut supports *layouts*, which are a way to centralizing common HTML amongst many
|
4
|
+
different pages. Conceptually, they are the same as a Rails layout. Technically, they are a Phlex component designed to render a page from a `yield` block.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
Your app should include `app/src/front_end/layouts/default_layout.rb`. The name
|
9
|
+
"default" is special, in that all pages will use this layout by default.
|
10
|
+
|
11
|
+
Since a layout is a Phlex component, its HTML is generated from `view_template`, and
|
12
|
+
it is expected to have exactly one `yield`, where the page's content will be
|
13
|
+
inserted.
|
14
|
+
|
15
|
+
```ruby {33}
|
16
|
+
class DefaultLayout < Brut::FrontEnd::Layout
|
17
|
+
include Brut::FrontEnd::Components
|
18
|
+
|
19
|
+
def initialize(page_name:)
|
20
|
+
@page_name = page_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def view_template
|
24
|
+
doctype
|
25
|
+
html(lang: "en") do
|
26
|
+
head do
|
27
|
+
meta(charset: "utf-8")
|
28
|
+
meta(content: "width=device-width,initial-scale=1", name:"viewport")
|
29
|
+
meta(content: "website", property:"og:type")
|
30
|
+
link(rel: "manifest", href: "/static/manifest.json")
|
31
|
+
link(rel: "preload", as: "style", href: asset_path("/css/styles.css"))
|
32
|
+
link(rel: "stylesheet", href: asset_path("/css/styles.css"))
|
33
|
+
script(defer: true, src: asset_path("/js/app.js"))
|
34
|
+
title { app_name }
|
35
|
+
PageIdentifier(@page_name)
|
36
|
+
I18nTranslations("cv.fe")
|
37
|
+
I18nTranslations("cv.this_field")
|
38
|
+
Traceparent()
|
39
|
+
render(
|
40
|
+
Brut::FrontEnd::RequestContext.inject(
|
41
|
+
Brut::FrontEnd::Components::LocaleDetection
|
42
|
+
)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
body do
|
46
|
+
brut_tracing url: "/__brut/instrumentation", show_warnings: true
|
47
|
+
main class: @page_name do
|
48
|
+
yield
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
### Maintaining Layouts
|
57
|
+
|
58
|
+
You are free to manage this how you like, however a few components inside the
|
59
|
+
`<head>` and `<body>` that are important to keep:
|
60
|
+
|
61
|
+
* `Brut::FrontEnd::Components::PageIdentifier` includes a `<meta>` tag with the page's name in it, which is handy for managing your end-to-end tests.
|
62
|
+
* `Brut::FrontEnd::Components::I18nTranslations` includes translatsion for common client-side constraint violations. See [Forms](/forms), [I18n](/i18n), and [JavaScript](/javascript) for more details on how this is used.
|
63
|
+
* `Brut::FrontEnd::Components::Traceparent` ensures that the OpenTelemetry *traceparent* is available so when client-side telemetry is reported back to the server, it can be connected to the request that initiated it.
|
64
|
+
* The `<brut-tracing>` element collects the client-side telemetry and sends it back
|
65
|
+
to the server.
|
66
|
+
|
67
|
+
### Creating Alternate Layouts
|
68
|
+
|
69
|
+
The way each page knows to use `DefaultLayout` is due to the `layout` method of
|
70
|
+
`Brut::FrontEnd::Page`, which returns `"default"`. The return value of `layout` is
|
71
|
+
used to figure out the name of the layout class.
|
72
|
+
|
73
|
+
You can set up your own by overriding `layout`:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class MyOtherPage < AppPage
|
77
|
+
def layout = "other_design"
|
78
|
+
|
79
|
+
# ...
|
80
|
+
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Brut will expect the class `OtherDesignLayout` to exist and provide HTML. Based on
|
85
|
+
Zeitwerk's conventions, that class should be in
|
86
|
+
`app/src/front_end/layouts/other_design_layout.rb`.
|
87
|
+
|
88
|
+
### No Layout
|
89
|
+
|
90
|
+
If you don't want a layout, you are encouraged to creat a blank layout, for example:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class BlankLayout < Brut::FrontEnd::Layout
|
94
|
+
def view_template
|
95
|
+
yield
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# use like so:
|
100
|
+
|
101
|
+
def layout = "blank"
|
102
|
+
```
|
103
|
+
|
104
|
+
## Testing
|
105
|
+
|
106
|
+
You generally don't test a layout, aside from end-to-end tests. If your layout
|
107
|
+
needs complex logic, you are encouraged to extract that to a
|
108
|
+
[component](/components) and test that instead.
|
109
|
+
|
110
|
+
## Recommended Practices
|
111
|
+
|
112
|
+
Keep your layouts as simple as you can.
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
## Technical Notes
|
117
|
+
|
118
|
+
> [!IMPORTANT]
|
119
|
+
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
120
|
+
> internals, the source code is always more correct.
|
121
|
+
|
122
|
+
_Last Updated July 1, 2025_
|
123
|
+
|
124
|
+
Layouts work due to the implementation of the method `view_template` in `Brut::FrontEnd::Page`. This is why a page class must provide `page_template` instead.
|
125
|
+
|
126
|
+
While you could override `view_template` in your page to provide a "blank layout",
|
127
|
+
this is discouraged, as the use of `view_template` should be considered a
|
128
|
+
private implementation detail.
|
129
|
+
|
130
|
+
|
data/brutrb.com/lsp.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Language Server Protocol (LSP) Support
|
2
|
+
|
3
|
+
Because Brut development happens inside Docker, but your editor likely runs on your
|
4
|
+
computer, getting LSP servers running takes a few more steps.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
When you created your app with `mkbrut`, the following LSP-related modules are set
|
9
|
+
up and/or installed:
|
10
|
+
|
11
|
+
* Shopify's Ruby LSP server (installed from `bin/setup`)
|
12
|
+
* Microsoft's TypeScript/JavaScript and CSS LSP serfvers (specified in `package.json`, installed when `npm install` runs from `bin/setup`)
|
13
|
+
|
14
|
+
In order to use them from your computer a few configurations are needed, some of
|
15
|
+
which Brut has done, and some you will need to do.
|
16
|
+
|
17
|
+
| Configuration | Description | Brut Handled? |
|
18
|
+
|---|---|---|
|
19
|
+
| Paths inside Docker Must Match Your Computer | When an LSP server communicates about a file, it does so with a path. That means that paths inside the Docker container must be the same as those on your computer. Brut achievecs this by using `${CWD}` inside `docker-compose.dx.yml` | ✅ |
|
20
|
+
| Third party libraries must *also* be installed in a path that is the same in both places | When jumping to a definition, the LSP server will again use paths, which must match. Because Node modules are installed local to your app, they already work. Ruby Gems, however, are configured to be installed in `local-gems` in your app. Brut should've added this to `.gitignore` and setup everything inside Docker to use it. | ✅ |
|
21
|
+
| Your editor must use `dx/exec` to execute LSP commands | Your editor will need to know that the LSP servers are running inside Docker. If your editor allows configuring the commands used to do this, you must prefix them with `dx/exec bashc -lc`. See [my blog post](https://naildrivin5.com/blog/2025/06/12/neovim-and-lsp-servers-working-with-docker-based-development.html) for details. | ❌ |
|
22
|
+
| Other languages or plugins to existing LSP servers | I haven't used these, so no idea how well they work. | ❌ |
|
23
|
+
|