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,182 @@
|
|
1
|
+
# Creating your Own Text Field Component
|
2
|
+
|
3
|
+
Brut's `Brut::FrontEnd::Components::Input::InputTag` creates only the `<input>` HTML
|
4
|
+
element. You will likely want something more sophsticated. You can achieve this by
|
5
|
+
creating your own component.
|
6
|
+
|
7
|
+
## Feature
|
8
|
+
|
9
|
+
We'll make a text field that has a label, error messages, and styling. It will
|
10
|
+
support three sizes: small, normal, and large.
|
11
|
+
|
12
|
+
It will require a form and an input name, and optional index as well.
|
13
|
+
|
14
|
+
## Recipe
|
15
|
+
|
16
|
+
### Create the Initializer
|
17
|
+
|
18
|
+
First, we'll create the component:
|
19
|
+
|
20
|
+
```
|
21
|
+
bin/scaffold component text_field
|
22
|
+
```
|
23
|
+
|
24
|
+
Now, edit the initializer to accept the parameters we need:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# app/src/front_end/components/text_field_component.rb
|
28
|
+
class TextFieldComponent < AppComponent
|
29
|
+
def initialize(form:,
|
30
|
+
input_name:,
|
31
|
+
index: 0, # default for non-array values
|
32
|
+
size: :normal)
|
33
|
+
@form = form
|
34
|
+
@input_name = input_name
|
35
|
+
@index = index
|
36
|
+
@size = size
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
### Outline the HTML
|
42
|
+
|
43
|
+
We'll want HTML like so:
|
44
|
+
|
45
|
+
```html
|
46
|
+
<label>
|
47
|
+
<input ...>
|
48
|
+
<span>LABEL HERE</span>
|
49
|
+
<brut-cv-messages></brut-cv-messages>
|
50
|
+
</label>
|
51
|
+
```
|
52
|
+
|
53
|
+
Before we worry about CSS or styling, let's sketch this out in `view_template`. The
|
54
|
+
actual label text will come from our I18n setup. We'll assume a "labels" top-level
|
55
|
+
section that has sections for each form and then inside that, each input name:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# app/config/i18n/en/2_app.rb
|
59
|
+
{
|
60
|
+
"labels": {
|
61
|
+
"LoginForm": {
|
62
|
+
"email": "Email addressed you used when singing up",
|
63
|
+
"password": "Your password",
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
```
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# app/src/front_end/components/text_field_component.rb
|
71
|
+
class TextFieldComponent < AppComponent
|
72
|
+
|
73
|
+
include Brut::FrontEnd::Components
|
74
|
+
|
75
|
+
private attr_reader :form, :input_name, :index
|
76
|
+
def view_template
|
77
|
+
label do
|
78
|
+
InputTag(form:, input_name:, index: )
|
79
|
+
span { raw(t([ "labels", form.class.name, input_name ])) }
|
80
|
+
ConstraintViolations(form:, input_name:, index:)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### Styling the Component
|
87
|
+
|
88
|
+
Styling can happen in a few ways. For simplicity, we'll use CSS and have minimal
|
89
|
+
classes on our HTML. Since the structure is all inside a `<label>`, we'll add a
|
90
|
+
class on that, named for our component. We'll also include form and input names
|
91
|
+
in the class to allow overriding if needed. Lastly, we'll include the
|
92
|
+
size as well.
|
93
|
+
|
94
|
+
```ruby {8-14}
|
95
|
+
# app/src/front_end/components/text_field_component.rb
|
96
|
+
class TextFieldComponent < AppComponent
|
97
|
+
|
98
|
+
include Brut::FrontEnd::Components
|
99
|
+
|
100
|
+
private attr_reader :form, :input_name, :index
|
101
|
+
def view_template
|
102
|
+
label_classes = [
|
103
|
+
class.name,
|
104
|
+
class.name + "-#{@size}",
|
105
|
+
form.class.name,
|
106
|
+
input_name
|
107
|
+
]
|
108
|
+
label(class: label_classes) do
|
109
|
+
InputTag(form:, input_name:, index: )
|
110
|
+
span { raw(t([ "labels", form.class.name, input_name ])) }
|
111
|
+
ConstraintViolations(form:, input_name:, index:)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
Let's create `app/src/front_end/css/TextFieldComponent.css`, which we'll need to
|
118
|
+
`@import`:
|
119
|
+
|
120
|
+
```css
|
121
|
+
/* app/src/front_end/css/index.css */
|
122
|
+
@import "TextFieldComponent.css";
|
123
|
+
```
|
124
|
+
|
125
|
+
The CSS will assume BrutCSS's design system is available:
|
126
|
+
|
127
|
+
```css
|
128
|
+
/* app/src/front_end/css/TextFieldComponent.css */
|
129
|
+
.TextFieldComponent {
|
130
|
+
label {
|
131
|
+
display: flex;
|
132
|
+
flex-direction: column;
|
133
|
+
gap: var(--sp-2);
|
134
|
+
}
|
135
|
+
input {
|
136
|
+
border: solid thin var(--gray-500);
|
137
|
+
border-radius: var(--br-3);
|
138
|
+
padding: var(--sp-2);
|
139
|
+
font-size: var(--fs-3);
|
140
|
+
}
|
141
|
+
span {
|
142
|
+
font-size: var(--fs-2);
|
143
|
+
font-stye: italic;
|
144
|
+
color: var(--gray-400);
|
145
|
+
}
|
146
|
+
/** We assume the general styling for brut-form
|
147
|
+
and brut-cv exists in index.css */
|
148
|
+
brut-cv {
|
149
|
+
color: red;
|
150
|
+
}
|
151
|
+
&.TextFieldComponent-small {
|
152
|
+
input {
|
153
|
+
font-size: var(--fs-1);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
&.TextFieldComponent-large {
|
157
|
+
input {
|
158
|
+
font-size: var(--fs-4);
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
```
|
163
|
+
|
164
|
+
### Using the Component
|
165
|
+
|
166
|
+
To use this component, we can create an instance and send it to Phlex's `render`:
|
167
|
+
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
def view_template
|
171
|
+
brut_form do
|
172
|
+
FormTag(for: @form) do
|
173
|
+
render TextFieldComponent.new(form: @form,
|
174
|
+
input_name: :name)
|
175
|
+
render TextFieldComponent.new(form: @form,
|
176
|
+
input_name: :quantity,
|
177
|
+
size: :small)
|
178
|
+
button { "Save" }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
```
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Roadmap to 1.0
|
2
|
+
|
3
|
+
A lot of Brut is solid, but there's several things missing from what I would
|
4
|
+
call a 1.0 release. Here are some ideas of what I think is needed:
|
5
|
+
|
6
|
+
## Better Dev Experience
|
7
|
+
|
8
|
+
* The output of `bin/dev` isn't great.
|
9
|
+
* otel-desktop-viewer is cool, but not the easiest to figure out issues as compred to good 'ole logging.
|
10
|
+
* Error pages in the app are *really* bad.
|
11
|
+
* CLI apps are OK, but could be fancier.
|
12
|
+
|
13
|
+
## More Tests
|
14
|
+
|
15
|
+
* Unit tests for all/most classes are needed. There's only a few now.
|
16
|
+
* Integration test of `mkbrut`, all automated.
|
17
|
+
* Web component/custom element tests need to be re-thought.
|
18
|
+
* Test output is a wall of text stack trace and this sucks.
|
19
|
+
* Improvements in access to Playwright features.
|
20
|
+
* Playright is the worst E2E testing tool except all the rest. Would love a better option here.
|
21
|
+
|
22
|
+
## More Complete Web Features
|
23
|
+
|
24
|
+
* Content security policy doens't allow for hashes, which can be limiting in some situations. I want everyone to be running with a CSP, so it has to be configurable to some degree.
|
25
|
+
* Websockets, server-push, etc. should be possible or at least have a recipe.
|
26
|
+
* Learn more about importmaps.
|
27
|
+
|
28
|
+
## Client-Side Improvements
|
29
|
+
|
30
|
+
BrutJS is woefully incomplete. I'd like developers to be able to accomplishe
|
31
|
+
certain tasks without needing a framework:
|
32
|
+
|
33
|
+
* Hooks into asset building to e.g. enable TailwindCSS or other tools.
|
34
|
+
* Better use of `fetch` in more situations
|
35
|
+
* Server-generated HTML replacement
|
36
|
+
* Better support for "API" style back-end when a framework *is* going to be used.
|
37
|
+
|
38
|
+
## Deployment
|
39
|
+
|
40
|
+
Out of the box support for more deployment mechanism, at least:
|
41
|
+
|
42
|
+
* Normal Heroku/`Procfile`-based deploy
|
43
|
+
* Digital Ocean-style hosting
|
44
|
+
* VPS?
|
45
|
+
|
46
|
+
## Documentation
|
47
|
+
|
48
|
+
* More recipes for how to do things
|
49
|
+
* More complete API docs with examples
|
50
|
+
* A unified look and feel across the board
|
51
|
+
* Get rid of VitePress for something less client-heavy, but still great
|
52
|
+
* Dash-accessible API docs
|
53
|
+
|
54
|
+
## Misc
|
55
|
+
|
56
|
+
* More direct Sidekiq support
|
57
|
+
|
data/brutrb.com/routes.md
CHANGED
@@ -1,26 +1,35 @@
|
|
1
1
|
# Routes
|
2
2
|
|
3
|
-
The primary function of a web framework like Brut is to map URLs requested by the browser or an HTTP client and invoke code based on
|
4
|
-
them.
|
3
|
+
The primary function of a web framework like Brut is to map URLs requested by the browser or an HTTP client and invoke code based on them.
|
5
4
|
|
6
|
-
Brut has a fairly simple routing system
|
7
|
-
will need as straigthforward as possible.
|
5
|
+
Brut has a fairly simple routing system that's not designed for flexibility.
|
8
6
|
|
9
7
|
## Overview
|
10
8
|
|
11
|
-
|
9
|
+
Your app has a subclass of `Brut::Framework::App`, called `App`. It includes a call
|
10
|
+
to the `routes` class method. In there, you declare your routes by using one of
|
11
|
+
four methods:
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
| Method | HTTP Method | Purpose |
|
14
|
+
|----------------------------------|-------------|-----------------------------------------------------------------------------------------------|
|
15
|
+
| `page «route»` | GET | Declare a page |
|
16
|
+
| `form «route»` | POST | Declare a form to be submitted to a handler |
|
17
|
+
| `action «route»` | POST | Declare an element-less form to be submitted to a handler (akin to Rails' `button_to` helper) |
|
18
|
+
| `path «route», method: «method»` | `«method»` | Declare an arbitrary path to a handler |
|
17
19
|
|
18
|
-
|
20
|
+
The value for `«route»`, along with the method called, is used to determine what
|
21
|
+
class(es) will be used to handle the route.
|
22
|
+
|
23
|
+
### «route» Syntax
|
24
|
+
|
25
|
+
A route is a string that contains the *path part* of a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL). *Segments* of the path (i.e. the stuff between each forward slash `/`) can be either *static* or a *placeholder*.
|
26
|
+
|
27
|
+
As such:
|
19
28
|
|
20
29
|
* Only the [pathname](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname) of a request may be specified.
|
21
30
|
* All routes must start with a slash
|
22
|
-
*
|
23
|
-
|
31
|
+
* A placeholder segment must be a valid Ruby identifier preceded by a colon, e.g.
|
32
|
+
`:company_id` is allowed, but `:company-id` is not.
|
24
33
|
* Routes may not start with a placeholder.
|
25
34
|
|
26
35
|
Some examples:
|
@@ -32,50 +41,23 @@ Some examples:
|
|
32
41
|
"/"
|
33
42
|
```
|
34
43
|
|
35
|
-
###
|
36
|
-
|
37
|
-
As mentioned above, routes are passed to methods that determine their purpose. There are currently four types of routes, and thus
|
38
|
-
four possible methods you would use to configure them:
|
39
|
-
|
40
|
-
|Method|Purpose| HTTP Method | More Info |
|
41
|
-
|------|-------|-------------|-----------|
|
42
|
-
|`page` | Specifies a web page at that route | `GET` | [Pages](/pages) |
|
43
|
-
|`form` | Indicates a form will exist and post its form data to this route | `POST` | [Forms](/pages) |
|
44
|
-
|`action` | Indicates a form with no form data will exist and post to this route | `POST` | [Handlers](/handlers) |
|
45
|
-
|`path` | This route will respond to an arbitrary HTTP method, which must be specified as an additional parameter | Any | [Handlers](/handlers) |
|
46
|
-
|
47
|
-
Brut is designed around generating HTML. HTML provides the ability to navigate to new web pages via `GET`, or submit data to the
|
48
|
-
server from a `<form>` via `POST`. That is why three of the four methods are focused on these use-cases.
|
49
|
-
|
50
|
-
To specify routes, you can call these methods inside the `routes do` block of your `App` class, located in `app/src/app.rb`:
|
51
|
-
|
52
|
-
```ruby{6-9} [app/src/app.rb]
|
53
|
-
class App < Brut::Framework::App
|
54
|
-
def id = "my-app"
|
55
|
-
def organization = "my-org"
|
56
|
-
|
57
|
-
routes do
|
58
|
-
page "/widgets/:id"
|
59
|
-
form "/new_widget"
|
60
|
-
action "/archive_widget/:id"
|
61
|
-
path "/widget_payment_received", method: :put
|
62
|
-
end
|
63
|
-
end
|
64
|
-
```
|
44
|
+
### Class Naming Conventions
|
65
45
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
> concepts a non-programmer can observe or identify, like URLs, forms, and pages.
|
46
|
+
Brut is convention-based, so you are not able to specify the name of the classes
|
47
|
+
used to handle routes. Brut will use the method you called (e.g. `page`) and the
|
48
|
+
route your provided to determine the class name.
|
70
49
|
|
71
|
-
|
50
|
+
Some examples:
|
72
51
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
52
|
+
| Route invocation | Expected Class Name(s) |
|
53
|
+
|-----------------------------------------------|------------------------------------|
|
54
|
+
| `page "/dashboard"` | `DashboardPage` |
|
55
|
+
| `page "/widgets/:id"` | `WidgetsByIdPage` |
|
56
|
+
| `form "/login"` | `LoginForm` and `LoginHandler` |
|
57
|
+
| `action "/delete_widget/:id"` | `DeleteWidgetWithIdHandler` |
|
58
|
+
| `path "/tokens/personal/:token, method :put"` | `Tokens::PersonalWithTokenHandler` |
|
77
59
|
|
78
|
-
|
60
|
+
Specifically, the name of the class(es) is/are determined as follows:
|
79
61
|
|
80
62
|
* Static segments of the pathname are mapped to namespaces or a class based on converting the path segment to camel-case. For example `new_widget` becomes `NewWidget`.
|
81
63
|
* The final static segment in the path represents a class name. All other static segments represent modules in which the final class is namespaced
|
@@ -89,15 +71,6 @@ The name of the class is determined as follows:
|
|
89
71
|
* These are now connected to form a valid Ruby class name.
|
90
72
|
* The route `/` is special and always maps to `HomePage`.
|
91
73
|
|
92
|
-
The examples in the previous section demonstrate how this works:
|
93
|
-
|
94
|
-
| Route | Class name |
|
95
|
-
|-------|------------|
|
96
|
-
| `page "/widgets/:id"` | `WidgetsByIdPage` |
|
97
|
-
| `form "/new_widget"` | `NewWidgetForm` and `NewWidgetHandler`
|
98
|
-
| `action "/archive_widget/:id"` | `ArchiveWidgetByIdHandler`
|
99
|
-
| `path "/widget_payment_received", method: :put` | `WidgetPaymentReceivedHandler`
|
100
|
-
|
101
74
|
Note that deeply nested routes that contain several placeholders will work, and create complicated classnames.
|
102
75
|
|
103
76
|
```ruby
|
@@ -105,10 +78,12 @@ page "/company/:company_id/location/:location_id"
|
|
105
78
|
# => CompanyByCompanyId::LocationByLocationIdPage
|
106
79
|
```
|
107
80
|
|
108
|
-
> [!
|
109
|
-
>
|
81
|
+
> [!NOTE]
|
82
|
+
> All routes can receive query string parameters. These are not factored
|
83
|
+
> into the name of the class that will handle the route, but they
|
84
|
+
> *are* made available to your Page or Handler.
|
110
85
|
|
111
|
-
### Creating URIs
|
86
|
+
### Creating URIs for Routes
|
112
87
|
|
113
88
|
Because each route is associated with a class, you can use the class to create the route, including any placeholders and query string
|
114
89
|
parameters.
|
@@ -120,6 +95,8 @@ The most direct way to do this is with the `routing` method available on each pa
|
|
120
95
|
# => /widgets/42
|
121
96
|
> WidgetsByIdPage.routing(id: 42, compact: true)
|
122
97
|
# => /widgets/42?compact=true
|
98
|
+
> WidgetsByIdPage.routing(id: 42, compact: true, anchor: "summary")
|
99
|
+
# => /widgets/42?compact=true#summary
|
123
100
|
> ArchiveWidgetByIdHandler.routing(id: 42)
|
124
101
|
# => /archive_widget/42
|
125
102
|
```
|
@@ -147,7 +124,7 @@ explaining the problem.
|
|
147
124
|
```
|
148
125
|
|
149
126
|
> [!NOTE]
|
150
|
-
> You can use `routing` to create `<form>` actions, but `
|
127
|
+
> You can use `routing` to create `<form>` actions, but `Brut::FrontEnd::Components::FormTag`, which we'll discuss in [Forms](/forms), can do this for you.
|
151
128
|
|
152
129
|
The `routing` method isn't an abstraction around routes. It's more of a strongly-typed translation. This means when you change
|
153
130
|
something, your app won't route to non-existent routes—it'll blow up with a helpful error.
|
@@ -157,40 +134,37 @@ rename the class. At this point, any code that routes to `DashboardPage.routing
|
|
157
134
|
|
158
135
|
## Testing
|
159
136
|
|
160
|
-
Routes are configuration, so you do not need to test them. Your end-to-end tests
|
137
|
+
Routes are configuration, so you do not need to test them. In fact, you can't test them directly. Your end-to-end tests should adequately cover the correct usage of your routes. If you always using `.routing` to generate routes, Ruby's runtime check swill also ensure you have not used a non-existent or invalid route.
|
161
138
|
|
162
139
|
## Recommended Practices
|
163
140
|
|
164
|
-
Brut does not provide flexibility with routes
|
165
|
-
|
166
|
-
routing layer.
|
167
|
-
|
168
|
-
Beyond these technical limitations, here are some recommendations regarding routes.
|
141
|
+
Brut does not provide flexibility with routes, nor is logic intended to exist where
|
142
|
+
you are declaring them.
|
169
143
|
|
170
144
|
### Routes Should be Named for Concepts Anyone Can Understand
|
171
145
|
|
172
|
-
|
146
|
+
If you have an account management page that allows modifying data in a table called `user_preferences`, but everyone just calls it "the account management page", the route should be `/account_management`.
|
173
147
|
|
174
|
-
Although routes are primarily for programmers
|
175
|
-
app uses. This is part of the reason Brut inserts `By` or `With` when there is a placeholder. It allows you to have a page for all
|
176
|
-
widgets—the "widgets page"—and a page for a specific widget by id—the "widgets by id page".
|
148
|
+
Although routes are primarily for programmers, there's no reason not to name them using the terms everyone involved in your app uses. This is part of the reason Brut inserts `By` or `With` when there is a placeholder. It allows you to have a page for all widgets—the "widgets page"—and a page for a specific widget by id—the "widgets by id page".
|
177
149
|
|
178
150
|
### Prefer Shallow Routes with a Single Placeholder
|
179
151
|
|
180
152
|
The more path segments your route has, and the more placeholders it is, the longer your class name will be and the more you lose the
|
181
153
|
connection to reality. The "company by company id location by location id page" doesn't exactly roll off the tongue.
|
182
154
|
|
183
|
-
Life will be easier if you can choose names and routes that have a single placeholder. Multiple path segments can be useful for
|
184
|
-
namespacing.
|
155
|
+
Life will be easier if you can choose names and routes that have a single placeholder. Multiple path segments can be useful for namespacing.
|
185
156
|
|
186
157
|
### Placeholders Identify Things, Query Strings Search for Things
|
187
158
|
|
188
|
-
|
189
|
-
|
190
|
-
|
159
|
+
A query string is for just that: querying. The query string is not for identifying
|
160
|
+
things. That's what URIs are for.
|
161
|
+
|
162
|
+
As such, for routes where a specific *thing* is being identified, use route
|
163
|
+
placeholders like `/widgets/:id`. When a route is used for searching or locating
|
164
|
+
*things*, a query string is better: `/widgets?type=«type»`.
|
191
165
|
|
192
|
-
|
193
|
-
|
166
|
+
Remember that the query string is *not* part of the class name. The values for the
|
167
|
+
query string will be made available to your page or handler.
|
194
168
|
|
195
169
|
### Pluralization Is Up to You
|
196
170
|
|
data/brutrb.com/security.md
CHANGED
@@ -100,6 +100,3 @@ This may change in the future.
|
|
100
100
|
Session cookies are set to expire after 1 year and use the value `Lax` for
|
101
101
|
[`SameSite`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#controlling_third-party_cookies_with_samesite). "Lax" allows other sites to submit the Brut-powered site's cookies, but only if the user navigates to the site. They are not sent if another site submits an Ajax request. We feel this is the right tradeoff betweeen usability and security.
|
102
102
|
|
103
|
-
### Content Security Policy headers and tools
|
104
|
-
|
105
|
-
### `bundle audit`
|
@@ -1,29 +1,25 @@
|
|
1
1
|
# Space/Time Continuum - Making Sense of Times and Time Zones
|
2
2
|
|
3
|
-
Time zones are the worst. But they are fact of life. This means that answer a question like "what is the date?"
|
4
|
-
or "is it Monday?" are not that easy to answer.
|
3
|
+
Time zones are the worst. But they are fact of life. This means that answer a question like "what is the date?" or "is it Monday?" are not that easy to answer. Brut tries to help.
|
5
4
|
|
6
5
|
## Timezones Outside of Web Requests
|
7
6
|
|
8
|
-
For back-end code, storing dates to the database, etc., Brut falls back to the normal Ruby app mechanisms for
|
9
|
-
determining the "current time zone", which is to say, it super duper depends. The system, the database, and Ruby
|
10
|
-
can all configure a time zone that is in effect when dates are parsed or stored.
|
7
|
+
For back-end code, storing dates to the database, etc., Brut falls back to the normal Ruby app mechanisms for determining the "current time zone", which is to say, it super-duper depends. The system, the database, and Ruby can all configure a time zone that is in effect when dates are parsed or stored.
|
11
8
|
|
12
|
-
The main way to deal with this is in how [Brut manages your database schema](/database-schema), which is to say that it
|
13
|
-
|
14
|
-
your system is set to UTC, stores a timestamp, then restarts with the time zone set to America/Los\_Angeles, that timestamp will be read back without ambiguity. If you use `timestamp without time zone` (
|
9
|
+
The main way to deal with this is in how [Brut manages your database schema](/database-schema), which is to say that it defaults to using `timestamp with time zone` and encourages you to do the same.
|
10
|
+
|
11
|
+
What this data type means is if your system is set to UTC, stores a timestamp, then restarts with the time zone set to America/Los\_Angeles, that timestamp will be read back without ambiguity. If you use `timestamp without time zone` (or SQL's standard `timestamp`), this will not be the case.
|
15
12
|
|
16
13
|
All this is to say, if you use `timestamp with time zone`, you should generally not have to worry about this.
|
17
14
|
Just be careful when serializing these values.
|
18
15
|
|
19
|
-
## Timezones for
|
16
|
+
## Timezones for Sessions
|
20
17
|
|
21
18
|
Depending on what your app does, you may need to show dates or times to the site visitor. And you'll probably
|
22
19
|
want to show those in their time zone. Brut can help with this.
|
23
20
|
|
24
21
|
As mentioned in [Flash and Session](/flash-and-session), the session provides access to timezone information.
|
25
22
|
Brut also provides a `Clock` class that represents the current date and time in the current session's time zone.
|
26
|
-
Here is how that works.
|
27
23
|
|
28
24
|
There are two ways to determine a visitor's time zone: you can ask them, or you can ask their browser.
|
29
25
|
|
@@ -51,8 +47,8 @@ If you have a way to ask the user what their timezone is, you can set it via `se
|
|
51
47
|
done this, *that* value is returned instead of the above logic.
|
52
48
|
|
53
49
|
Note that in all cases, the timezone that is serialized into the session is the name. This means it's technically
|
54
|
-
possible for the name to valid when stored and invalid when read, if you have updated the `tzinfo` gem and
|
55
|
-
something changed.
|
50
|
+
possible for the name to be valid when stored and invalid when read, if you have updated the `tzinfo` gem and
|
51
|
+
something changed (this should be exceedingly rare).
|
56
52
|
|
57
53
|
Therefore, it's recommended that if you have asked the visitor their preferred time zone, you store that
|
58
54
|
somewhere in the database, so you can detect when the value from the session has drifted.
|
data/brutrb.com/tutorial.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
1
|
# Tutorial
|
2
2
|
|
3
|
-
|
3
|
+
A real tutorial will appear here eventually.
|
4
|
+
|
5
|
+
In the mean time, [ADRs.cloud](https://github.com/thirdtank/adrs.cloud) is a
|
6
|
+
full-fledge Brut app you can examine to see how things work. You can be running it
|
7
|
+
locally in minutes, once you install Docker.
|
data/brutrb.com/why.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Why Does Brut Exist?
|
2
|
+
|
3
|
+
I love writing Ruby, but grew tired of writing Rails. Rails is great, and has been
|
4
|
+
great to me over the years. I've written a lot of books about it! But the churn and
|
5
|
+
increasing configuration burden made me think: what if we had another way to build
|
6
|
+
web apps in Ruby?
|
7
|
+
|
8
|
+
What if it was totally different, but still focused on being straightforward and
|
9
|
+
simple? What if it had *fewer* abstractions, *less* configuration, and not as much
|
10
|
+
*stuff*?
|
11
|
+
|
12
|
+
My thinking is, you need to know HTML, JavaScript, CSS, SQL, Ruby, HTTP, and a few
|
13
|
+
other things to make a web app. What if we tried to limit the additional
|
14
|
+
abstractions you'd have to learn?
|
15
|
+
|
16
|
+
That's what Brut is trying to be. Straightfoward, direct abstractions or
|
17
|
+
translations of stuff you already know. The raw web…or at least as raw as it can be.
|
18
|
+
|
19
|
+
|
data/docs/404.html
CHANGED
@@ -6,16 +6,21 @@
|
|
6
6
|
<title>404 | Brut RB</title>
|
7
7
|
<meta name="description" content="Not Found">
|
8
8
|
<meta name="generator" content="VitePress v1.6.3">
|
9
|
-
<link rel="preload stylesheet" href="/assets/style.
|
9
|
+
<link rel="preload stylesheet" href="/assets/style.B1z60PPQ.css" as="style">
|
10
10
|
<link rel="preload stylesheet" href="/vp-icons.css" as="style">
|
11
11
|
|
12
|
-
<script type="module" src="/assets/app.
|
12
|
+
<script type="module" src="/assets/app.Dm3x-DQc.js"></script>
|
13
|
+
<link rel="icon" href="/favicon.ico">
|
14
|
+
<meta property="og:title" content="BrutRB Documentation">
|
15
|
+
<meta property="og:type" content="website">
|
16
|
+
<meta property="og:image" content="https://brutrb.com/SocialImage.png">
|
17
|
+
<script defer data-domain="brutrb.com" src="https://plausible.io/js/script.js"></script>
|
13
18
|
<script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
|
14
19
|
<script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
|
15
20
|
</head>
|
16
21
|
<body>
|
17
22
|
<div id="app"></div>
|
18
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"ai.md\":\"
|
23
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"JRxZ5uYE\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"Pg_Lo35G\",\"configuration.md\":\"BfeGnEci\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"CSYk6E6v\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"Dy6EldaM\",\"dir-structure.md\":\"CWir1pic\",\"doc-conventions.md\":\"DOkAuXlt\",\"end-to-end-tests.md\":\"DzqRpZ43\",\"features.md\":\"DPFXsy0z\",\"flash-and-session.md\":\"nPvUpnUx\",\"form-constraints.md\":\"x5tNpTTI\",\"forms.md\":\"BQZlCwvi\",\"getting-started.md\":\"BcXnNuD6\",\"handlers.md\":\"Chyri6KA\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"xQhiGo1G\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CJGDFY-m\",\"lsp.md\":\"Dn1rIiW0\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"overview.md\":\"iMnwLO4x\",\"pages.md\":\"B7Hc-i6H\",\"recipes_alternate-layouts.md\":\"BwEytl59\",\"recipes_authentication.md\":\"Dzvi_g69\",\"recipes_blank-layouts.md\":\"fyAUJyJR\",\"recipes_custom-flash.md\":\"CrQbI5eH\",\"recipes_indexed-forms.md\":\"CstYyOSo\",\"recipes_text-field-component.md\":\"H4wLAK0Z\",\"roadmap.md\":\"C6PRi0DX\",\"routes.md\":\"B8kfUPHU\",\"security.md\":\"C0G_AZR-\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"xl44xDos\",\"tutorial.md\":\"BYXj4cOu\",\"unit-tests.md\":\"DUGrnLj5\",\"why.md\":\"C-hk5xgJ\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":true,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"},{\"text\":\"LSP Support\",\"link\":\"/lsp\"}]},{\"text\":\"Recipes\",\"collapsed\":true,\"items\":[{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Blank Layouts\",\"link\":\"/recipes/blank-layouts\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
|
19
24
|
|
20
25
|
</body>
|
21
26
|
</html>
|
Binary file
|