brut 0.4.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/CHANGELOG.md +36 -1
- data/Dockerfile.dx +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +19 -0
- data/assets/MetroIcon.graffle +0 -0
- data/assets/YouTubeThumb.pxd +0 -0
- data/bin/build +86 -0
- data/bin/ci +36 -0
- data/bin/docs +39 -9
- data/bin/publish +61 -0
- data/bin/setup +6 -0
- data/brut-css/bin/build +19 -0
- data/brut-css/bin/ci +19 -0
- data/brut-css/bin/docs +19 -0
- data/brut-css/bin/publish +21 -0
- data/brut-css/bin/setup +1 -0
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-css/src/css/flex.css +1 -1
- data/brut-css/src/docs/includes/body-and-header.html.ejs +1 -1
- data/brut-js/bin/build +15 -6
- data/brut-js/bin/docs +25 -0
- data/brut-js/bin/publish +21 -0
- data/brut-js/bin/setup +1 -0
- data/brut-js/dx +1 -0
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brut-js/specs/AjaxSubmit.spec.js +100 -7
- data/brut-js/src/AjaxSubmit.js +76 -40
- data/brut.gemspec +2 -2
- data/brutrb.com/adrs.md +1 -0
- data/brutrb.com/bin/setup +1 -0
- data/brutrb.com/getting-started.md +3 -0
- data/brutrb.com/overview.md +6 -0
- data/brutrb.com/tutorial.md +7 -3
- data/docs/404.html +2 -2
- data/docs/adrs.html +5 -5
- data/docs/ai.html +3 -3
- 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 +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +2 -2
- 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 +1 -1
- 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 +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- 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 +86 -2
- data/docs/api/Brut/FrontEnd/Component.html +78 -9
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Input.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +3 -3
- 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 +1 -1
- 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 +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 +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 +3 -3
- 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 +31 -23
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +3 -3
- 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 +13 -11
- 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/ModuleName.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 +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 +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/method_list.html +92 -76
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{adrs.md.JRxZ5uYE.js → adrs.md.BxjHi9-8.js} +1 -1
- data/docs/assets/adrs.md.BxjHi9-8.lean.js +1 -0
- data/docs/assets/{app.AkS4fHzI.js → app.DyQLb4Ot.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.CmtZyrFA.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.By6un1FD.js → VPLocalSearchBox.T1iA-eJx.js} +1 -1
- data/docs/assets/chunks/{theme.DwiUPdci.js → theme.ChwsbWjK.js} +2 -2
- data/docs/assets/{components.md.BfeWD9sX.js → components.md.DHh-NwKs.js} +3 -3
- data/docs/assets/{configuration.md.DoSBNc0H.js → configuration.md.D8Wz3oJU.js} +1 -1
- data/docs/assets/{forms.md.DFveP5g_.js → forms.md.BRE85eju.js} +1 -1
- data/docs/assets/{getting-started.md.DZWjCVC0.js → getting-started.md.2ioiTe-B.js} +6 -3
- data/docs/assets/{getting-started.md.DZWjCVC0.lean.js → getting-started.md.2ioiTe-B.lean.js} +1 -1
- data/docs/assets/overview.md.DlKiRRG_.js +1 -0
- data/docs/assets/overview.md.DlKiRRG_.lean.js +1 -0
- data/docs/assets/tutorial.md.BIb7XT6j.js +1 -0
- data/docs/assets/tutorial.md.BIb7XT6j.lean.js +1 -0
- data/docs/assets.html +3 -3
- data/docs/brut-css/brut.max.css +1 -1
- data/docs/brut-css/classes/appearances.html +1 -1
- data/docs/brut-css/classes/background-colors.html +1 -1
- data/docs/brut-css/classes/border-colors.html +1 -1
- data/docs/brut-css/classes/borders.html +1 -1
- data/docs/brut-css/classes/dimensions.html +1 -1
- data/docs/brut-css/classes/flex.html +3 -3
- data/docs/brut-css/classes/foreground-colors.html +1 -1
- data/docs/brut-css/classes/junk-drawer.html +1 -1
- data/docs/brut-css/classes/layout.html +1 -1
- data/docs/brut-css/classes/lists.html +1 -1
- data/docs/brut-css/classes/positioning.html +1 -1
- data/docs/brut-css/classes/spacings.html +1 -1
- data/docs/brut-css/classes/typography.html +1 -1
- data/docs/brut-css/customization/advanced-configuration.html +1 -1
- data/docs/brut-css/customization/breakpoints.html +1 -1
- data/docs/brut-css/customization/design-system.html +1 -1
- data/docs/brut-css/customization/pseudo-classes.html +1 -1
- data/docs/brut-css/getting-started/core-concepts.html +1 -1
- data/docs/brut-css/getting-started/installation.html +1 -1
- data/docs/brut-css/getting-started/overview.html +1 -1
- data/docs/brut-css/getting-started/simple-example.html +1 -1
- data/docs/brut-css/index.html +1 -1
- data/docs/brut-css/properties/colors.html +1 -1
- data/docs/brut-css/properties/spacings.html +1 -1
- data/docs/brut-css/properties/typography.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.html +56 -7
- data/docs/brut-js/api/AjaxSubmit.js.html +77 -41
- 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 +3 -3
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +9 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +5 -5
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +3 -3
- data/docs/recipes/blank-layouts.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +5 -5
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/lib/brut/cli/apps/scaffold.rb +1 -1
- data/lib/brut/framework/mcp.rb +1 -1
- data/lib/brut/front_end/component.rb +19 -0
- data/lib/brut/front_end/components/form_tag.rb +2 -2
- data/lib/brut/front_end/page.rb +11 -7
- data/lib/brut/sinatra_helpers.rb +1 -0
- data/lib/brut/version.rb +1 -1
- data/mkbrut/.gitignore +16 -0
- data/mkbrut/CODE_OF_CONDUCT.txt +100 -0
- data/mkbrut/Gemfile +3 -0
- data/mkbrut/Gemfile.lock +19 -0
- data/mkbrut/LICENSE.txt +370 -0
- data/mkbrut/README.md +145 -0
- data/mkbrut/Rakefile +2 -0
- data/mkbrut/bin/build +36 -0
- data/mkbrut/bin/ci +19 -0
- data/mkbrut/bin/docs +19 -0
- data/mkbrut/bin/publish +129 -0
- data/mkbrut/bin/rake +16 -0
- data/mkbrut/bin/setup +30 -0
- data/mkbrut/brut-welcome.png +0 -0
- data/mkbrut/deploy/.dockerignore +2 -0
- data/mkbrut/deploy/Dockerfile +25 -0
- data/mkbrut/exe/mkbrut +5 -0
- data/mkbrut/lib/mkbrut/app.rb +79 -0
- data/mkbrut/lib/mkbrut/app_id.rb +8 -0
- data/mkbrut/lib/mkbrut/app_name.rb +29 -0
- data/mkbrut/lib/mkbrut/app_options.rb +36 -0
- data/mkbrut/lib/mkbrut/base.rb +57 -0
- data/mkbrut/lib/mkbrut/cli.rb +107 -0
- data/mkbrut/lib/mkbrut/erb_binding_delegate.rb +20 -0
- data/mkbrut/lib/mkbrut/internet_identifier.rb +32 -0
- data/mkbrut/lib/mkbrut/invalid_identifier.rb +4 -0
- data/mkbrut/lib/mkbrut/ops/add_css_import.rb +42 -0
- data/mkbrut/lib/mkbrut/ops/add_i18n_message.rb +74 -0
- data/mkbrut/lib/mkbrut/ops/add_method.rb +48 -0
- data/mkbrut/lib/mkbrut/ops/append_to_file.rb +20 -0
- data/mkbrut/lib/mkbrut/ops/base_op.rb +21 -0
- data/mkbrut/lib/mkbrut/ops/copy_file.rb +12 -0
- data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
- data/mkbrut/lib/mkbrut/ops/insert_route.rb +52 -0
- data/mkbrut/lib/mkbrut/ops/mkdir.rb +13 -0
- data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
- data/mkbrut/lib/mkbrut/ops/render_template.rb +26 -0
- data/mkbrut/lib/mkbrut/ops/skip_file.rb +10 -0
- data/mkbrut/lib/mkbrut/ops.rb +16 -0
- data/mkbrut/lib/mkbrut/organization.rb +5 -0
- data/mkbrut/lib/mkbrut/prefix.rb +26 -0
- data/mkbrut/lib/mkbrut/prefixed_io.rb +16 -0
- data/mkbrut/lib/mkbrut/segments/bare_bones.rb +185 -0
- data/mkbrut/lib/mkbrut/segments/demo.rb +121 -0
- data/mkbrut/lib/mkbrut/segments/heroku.rb +30 -0
- data/mkbrut/lib/mkbrut/segments/sidekiq.rb +3 -0
- data/mkbrut/lib/mkbrut/segments.rb +8 -0
- data/mkbrut/lib/mkbrut/version.rb +3 -0
- data/mkbrut/lib/mkbrut/versions.rb +13 -0
- data/mkbrut/lib/mkbrut.rb +18 -0
- data/mkbrut/mkbrut.gemspec +32 -0
- data/mkbrut/templates/Base/.dockerignore +25 -0
- data/mkbrut/templates/Base/.env.development.erb +60 -0
- data/mkbrut/templates/Base/.env.test.erb +8 -0
- data/mkbrut/templates/Base/.gitignore +31 -0
- data/mkbrut/templates/Base/.projections.json +59 -0
- data/mkbrut/templates/Base/Dockerfile.dx +205 -0
- data/mkbrut/templates/Base/Gemfile.erb +53 -0
- data/mkbrut/templates/Base/Procfile.development +5 -0
- data/mkbrut/templates/Base/Procfile.test +1 -0
- data/mkbrut/templates/Base/README.md +4 -0
- data/mkbrut/templates/Base/README.md.erb +40 -0
- data/mkbrut/templates/Base/app/bootstrap.rb +61 -0
- data/mkbrut/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
- data/mkbrut/templates/Base/app/config/i18n/en/2_app.rb +24 -0
- data/mkbrut/templates/Base/app/public/static/manifest.json.erb +33 -0
- data/mkbrut/templates/Base/app/src/app.rb.erb +37 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/db.rb +19 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb +6 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
- data/mkbrut/templates/Base/app/src/front_end/components/app_component.rb +8 -0
- data/mkbrut/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
- data/mkbrut/templates/Base/app/src/front_end/css/index.css +2 -0
- data/mkbrut/templates/Base/app/src/front_end/css/svgs.css +12 -0
- data/mkbrut/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
- data/mkbrut/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
- data/mkbrut/templates/Base/app/src/front_end/images/LogoPylon.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/LogoTransit.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/favicon.ico +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/icon.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
- data/mkbrut/templates/Base/app/src/front_end/js/index.js +6 -0
- data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +73 -0
- data/mkbrut/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
- data/mkbrut/templates/Base/app/src/front_end/pages/home_page.rb +62 -0
- data/mkbrut/templates/Base/app/src/front_end/support/app_session.rb +6 -0
- data/mkbrut/templates/Base/app/src/front_end/svgs/README.md +5 -0
- data/mkbrut/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
- data/mkbrut/templates/Base/bin/README.md.erb +5 -0
- data/mkbrut/templates/Base/bin/build-assets +7 -0
- data/mkbrut/templates/Base/bin/ci +39 -0
- data/mkbrut/templates/Base/bin/console +31 -0
- data/mkbrut/templates/Base/bin/db +9 -0
- data/mkbrut/templates/Base/bin/dbconsole +51 -0
- data/mkbrut/templates/Base/bin/dev +25 -0
- data/mkbrut/templates/Base/bin/release +26 -0
- data/mkbrut/templates/Base/bin/run +86 -0
- data/mkbrut/templates/Base/bin/scaffold +9 -0
- data/mkbrut/templates/Base/bin/setup +256 -0
- data/mkbrut/templates/Base/bin/startup-message +65 -0
- data/mkbrut/templates/Base/bin/test +9 -0
- data/mkbrut/templates/Base/bin/test-server +29 -0
- data/mkbrut/templates/Base/bin/watch-and-build-assets +37 -0
- data/mkbrut/templates/Base/config.ru +16 -0
- data/mkbrut/templates/Base/docker-compose.dx.yml +92 -0
- data/mkbrut/templates/Base/dx/README.md +28 -0
- data/mkbrut/templates/Base/dx/bash_customizations +12 -0
- data/mkbrut/templates/Base/dx/bash_customizations.local +8 -0
- data/mkbrut/templates/Base/dx/build +107 -0
- data/mkbrut/templates/Base/dx/docker-compose.env.erb +25 -0
- data/mkbrut/templates/Base/dx/dx.sh.lib +137 -0
- data/mkbrut/templates/Base/dx/exec +68 -0
- data/mkbrut/templates/Base/dx/prune +19 -0
- data/mkbrut/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
- data/mkbrut/templates/Base/dx/start +30 -0
- data/mkbrut/templates/Base/dx/stop +23 -0
- data/mkbrut/templates/Base/package.json.erb +37 -0
- data/mkbrut/templates/Base/puma.config.rb +53 -0
- data/mkbrut/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
- data/mkbrut/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
- data/mkbrut/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
- data/mkbrut/templates/Base/specs/lint_factories.spec.rb +7 -0
- data/mkbrut/templates/Base/specs/spec_helper.rb +78 -0
- data/mkbrut/templates/Base/specs/support.rb +2 -0
- data/mkbrut/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
- data/mkbrut/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
- data/mkbrut/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
- data/mkbrut/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
- data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
- data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +14 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/css/fonts.css +19 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
- data/mkbrut/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
- data/mkbrut/templates/segments/Demo/specs/factories/db/guestbook_message.factory.rb +7 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
- data/mkbrut/templates/segments/Heroku/bin/deploy +11 -0
- data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +125 -0
- data/mkbrut/templates/segments/Heroku/deploy/docker-entrypoint +15 -0
- data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +26 -0
- metadata +188 -24
- data/brut-js/CHANGELOG.md +0 -5
- data/docs/assets/adrs.md.JRxZ5uYE.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.C9FqcQxD.js +0 -1
- data/docs/assets/overview.md.iMnwLO4x.js +0 -1
- data/docs/assets/overview.md.iMnwLO4x.lean.js +0 -1
- data/docs/assets/tutorial.md.BYXj4cOu.js +0 -1
- data/docs/assets/tutorial.md.BYXj4cOu.lean.js +0 -1
- /data/docs/assets/{components.md.BfeWD9sX.lean.js → components.md.DHh-NwKs.lean.js} +0 -0
- /data/docs/assets/{configuration.md.DoSBNc0H.lean.js → configuration.md.D8Wz3oJU.lean.js} +0 -0
- /data/docs/assets/{forms.md.DFveP5g_.lean.js → forms.md.BRE85eju.lean.js} +0 -0
data/brut-js/package.json
CHANGED
@@ -10,7 +10,7 @@ describe("<brut-ajax-submit>", () => {
|
|
10
10
|
</brut-ajax-submit>
|
11
11
|
</form>
|
12
12
|
`).onFetch( "/foo", [
|
13
|
-
{ then: { status: 200 }},
|
13
|
+
{ then: { status: 200, text: "<div>some html</div>" }},
|
14
14
|
]
|
15
15
|
).test("submits the form, setting various attributes during the lifecycle",
|
16
16
|
({document,assert,fetchRequests,waitForSetTimeout,readRequestBodyIntoString}) => {
|
@@ -21,8 +21,10 @@ describe("<brut-ajax-submit>", () => {
|
|
21
21
|
const number = document.querySelector("input[name=some-number]")
|
22
22
|
|
23
23
|
let okReceived = false
|
24
|
-
|
24
|
+
let detailReceived = null
|
25
|
+
element.addEventListener("brut:submitok", (event) => {
|
25
26
|
okReceived = true
|
27
|
+
detailReceived = event.detail
|
26
28
|
})
|
27
29
|
|
28
30
|
text.value = "Some Text"
|
@@ -37,6 +39,8 @@ describe("<brut-ajax-submit>", () => {
|
|
37
39
|
|
38
40
|
return Promise.all(promises).then( () => {
|
39
41
|
assert(okReceived)
|
42
|
+
assert(detailReceived)
|
43
|
+
assert.equal(detailReceived.body.innerHTML,"<div>some html</div>")
|
40
44
|
assert(element.getAttribute("requesting") == null)
|
41
45
|
assert(element.getAttribute("submitted") != null)
|
42
46
|
waitForSetTimeout(11).then( () => {
|
@@ -58,7 +62,7 @@ describe("<brut-ajax-submit>", () => {
|
|
58
62
|
const number = form.querySelector("input[name=some-number]")
|
59
63
|
|
60
64
|
let okReceived = false
|
61
|
-
element.addEventListener("brut:submitok", () => {
|
65
|
+
element.addEventListener("brut:submitok", (event) => {
|
62
66
|
okReceived = true
|
63
67
|
})
|
64
68
|
|
@@ -204,16 +208,22 @@ Error that should be ignored
|
|
204
208
|
}
|
205
209
|
},
|
206
210
|
]
|
207
|
-
).test("when we get a 422, parses the result from the server", ({document,window,assert,fetchRequests,waitForSetTimeout}) => {
|
211
|
+
).test("when we get a 422, parses the result from the server and inserts them into the DOM, event detail is null", ({document,window,assert,fetchRequests,waitForSetTimeout}) => {
|
208
212
|
const form = document.querySelector("form")
|
209
213
|
const element = form.querySelector("brut-ajax-submit")
|
210
214
|
const button = element.querySelector("button")
|
211
215
|
const text = form.querySelector("input[name=some-text]")
|
212
216
|
const number = form.querySelector("input[name=some-number]")
|
213
217
|
|
214
|
-
let okReceived =
|
218
|
+
let okReceived = false
|
219
|
+
let submittedInvalidReceived = false
|
220
|
+
let submittedInvalidDetail = "not null"
|
215
221
|
element.addEventListener("brut:submitok", () => {
|
216
|
-
okReceived
|
222
|
+
okReceived = true
|
223
|
+
})
|
224
|
+
element.addEventListener("brut:submitinvalid", (event) => {
|
225
|
+
submittedInvalidReceived = true
|
226
|
+
submittedInvalidDetail = event.detail
|
217
227
|
})
|
218
228
|
|
219
229
|
text.value = "Some Text"
|
@@ -227,7 +237,9 @@ Error that should be ignored
|
|
227
237
|
map( (fetchRequest) => fetchRequest.promiseReturned )
|
228
238
|
|
229
239
|
return Promise.all(promises).then( () => {
|
230
|
-
assert
|
240
|
+
assert(!okReceived)
|
241
|
+
assert( submittedInvalidReceived)
|
242
|
+
assert(!submittedInvalidDetail)
|
231
243
|
const textFieldErrors = form.querySelectorAll("brut-cv-messages[input-name='some-text'] brut-cv")
|
232
244
|
assert.equal(3,textFieldErrors.length) // prevous client-side and 2 new server-side
|
233
245
|
const serverSideTextFieldErrors = form.querySelectorAll("brut-cv-messages[input-name='some-text'] brut-cv[server-side]")
|
@@ -253,4 +265,85 @@ Error that should be ignored
|
|
253
265
|
})
|
254
266
|
})
|
255
267
|
})
|
268
|
+
withHTML(`
|
269
|
+
<form action="http://example.net/foo" method="POST">
|
270
|
+
|
271
|
+
<input required type="text" name="some-text">
|
272
|
+
<brut-cv-messages input-name="some-text">
|
273
|
+
<brut-cv input-name="some-text">
|
274
|
+
A client-side-error
|
275
|
+
</brut-cv>
|
276
|
+
<brut-cv server-side input-name="some-text">
|
277
|
+
A previous server-side-error
|
278
|
+
</brut-cv>
|
279
|
+
</brut-cv-messages>
|
280
|
+
|
281
|
+
<input required type="number" name="some-number">
|
282
|
+
<brut-cv-messages input-name="some-number">
|
283
|
+
</brut-cv-messages>
|
284
|
+
|
285
|
+
<brut-ajax-submit submitted-lifetime="10" no-server-side-error-parsing>
|
286
|
+
<button>Submit</button>
|
287
|
+
</brut-ajax-submit>
|
288
|
+
</form>
|
289
|
+
`).onFetch( "/foo", [
|
290
|
+
{
|
291
|
+
then: {
|
292
|
+
status: 422,
|
293
|
+
text: `
|
294
|
+
<brut-cv input-name="some-text">
|
295
|
+
A sever-side error
|
296
|
+
</brut-cv>
|
297
|
+
<brut-cv input-name="some-text">
|
298
|
+
Another sever-side error
|
299
|
+
</brut-cv>
|
300
|
+
<brut-cv input-name="some-other-text">
|
301
|
+
Irrelevant server-side error
|
302
|
+
</brut-cv>
|
303
|
+
<brut-cv>
|
304
|
+
Error that should be ignored
|
305
|
+
</brut-cv>
|
306
|
+
<div>element that should be ignored</div>
|
307
|
+
`
|
308
|
+
}
|
309
|
+
},
|
310
|
+
]
|
311
|
+
).test("when we get a 422, does not parse the results, but includes them in the detail", ({document,window,assert,fetchRequests,waitForSetTimeout}) => {
|
312
|
+
const form = document.querySelector("form")
|
313
|
+
const element = form.querySelector("brut-ajax-submit")
|
314
|
+
const button = element.querySelector("button")
|
315
|
+
const text = form.querySelector("input[name=some-text]")
|
316
|
+
const number = form.querySelector("input[name=some-number]")
|
317
|
+
|
318
|
+
let okReceived = false
|
319
|
+
let submittedInvalidReceived = false
|
320
|
+
let submittedInvalidDetail = null
|
321
|
+
element.addEventListener("brut:submitok", () => {
|
322
|
+
okReceived = true
|
323
|
+
})
|
324
|
+
element.addEventListener("brut:submitinvalid", (event) => {
|
325
|
+
submittedInvalidReceived = true
|
326
|
+
submittedInvalidDetail = event.detail
|
327
|
+
})
|
328
|
+
|
329
|
+
text.value = "Some Text"
|
330
|
+
number.value = "11"
|
331
|
+
|
332
|
+
button.click()
|
333
|
+
return waitForSetTimeout(5).then( () => {
|
334
|
+
|
335
|
+
const promises = fetchRequests.
|
336
|
+
filter( (fetchRequest) => fetchRequest.promiseReturned ).
|
337
|
+
map( (fetchRequest) => fetchRequest.promiseReturned )
|
338
|
+
|
339
|
+
return Promise.all(promises).then( () => {
|
340
|
+
assert(!okReceived)
|
341
|
+
assert( submittedInvalidReceived)
|
342
|
+
assert.equal(submittedInvalidDetail.body.children.length,5)
|
343
|
+
const textFieldErrors = form.querySelectorAll("brut-cv-messages[input-name='some-text'] brut-cv")
|
344
|
+
assert.equal(2,textFieldErrors.length) // what was initially rendered
|
345
|
+
|
346
|
+
})
|
347
|
+
})
|
348
|
+
})
|
256
349
|
})
|
data/brut-js/src/AjaxSubmit.js
CHANGED
@@ -13,7 +13,13 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
13
13
|
* 4. If the request returns OK:
|
14
14
|
* - `requesting` will be removed and `submitted` will be added.
|
15
15
|
* - `submitted` will be removed after `submitted-lifetime` ms.
|
16
|
-
*
|
16
|
+
* - the `brut:submitok` event will be fired with the response text, **parsed as HTML**, as `event.detail`.
|
17
|
+
* 5. If the request returned a 422:
|
18
|
+
* - If you have set `no-server-side-error-parsing`, the results will be included in the
|
19
|
+
* detail field of the `brut:submitinvalid` event.
|
20
|
+
* - If you have NOT set `no-server-side-error-parsing`, the response is parsed as
|
21
|
+
* errors to be inserted into the DOM. See below for how that works. In this case,
|
22
|
+
* `brut:submitinvalid`'s detail bill be null.
|
17
23
|
* 6. If the request returns not OK and not 422:
|
18
24
|
* - if it has been `request-timeout` ms or more since the button was first clicked, the operation is aborted (see below).
|
19
25
|
* - if it has been less than `request-timeout` ms and the HTTP status code was 5xx, the operation is retried.
|
@@ -23,7 +29,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
23
29
|
* Aborting the operation will submit the form in the normal way, allowing the browser to deal with whatever the issue is. You can set
|
24
30
|
* `log-request-errors` to introspect this process.
|
25
31
|
*
|
26
|
-
* For a 422 response
|
32
|
+
* For a 422 response (where `no-server-side-error-parsing` is *not* set),
|
33
|
+
* this element assumes the response is `text/html` and contains one or more `<brut-cv>`
|
27
34
|
* elements. These elements will be inserted into the proper `<brut-cv-messages>` element, as follows:
|
28
35
|
*
|
29
36
|
* 1. The `input-name` is examined.
|
@@ -37,6 +44,17 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
37
44
|
* 9. The first input located is scrolled into view
|
38
45
|
* 10. If the input is modified after this all happens, custom validity is cleared
|
39
46
|
*
|
47
|
+
* For the server you are contacting, this element has a few requirements:
|
48
|
+
*
|
49
|
+
* - If everything is OK/the operation did what it was intended to do:
|
50
|
+
* - the server will respond with a 2xx
|
51
|
+
* - the response body, if it contains anything, be `text/html` (this is provided in the event detail)
|
52
|
+
* - If there are server-side constraint violations.
|
53
|
+
* - the server will return 422
|
54
|
+
* - the response body will be `text/html`
|
55
|
+
* - the response body will contain one or more `<brut-cv>` elements
|
56
|
+
*
|
57
|
+
* @property {boolean} no-server-side-error-parsing - if set, the response body for a 422 will not be parsed and inserted into the DOM. Instead, the body will be part of the detail of the `brut:submitinvalid` event.
|
40
58
|
* @property {number} request-timeout - number of ms that the entire operation is expected to complete within. Default is 5000
|
41
59
|
* @property {number} submitted-lifetime - number of ms that "submitted" should remain on the element after the form has completed. Default is 2000
|
42
60
|
* @property {boolean} requesting - boolean attribute that indicates the request has been made, but not yet returned. Don't set this yourself outside of development. It will be set and removed by this element.
|
@@ -44,8 +62,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
|
|
44
62
|
* @property {boolean} log-request-errors - if set, logging related to request error handling will appear in the console. It will also
|
45
63
|
* cause any form submission to be delayed by 2s to allow you to read the console.
|
46
64
|
*
|
47
|
-
* @fires brut:submitok Fired when the AJAX request initated by this returns OK and all processing has completed
|
48
|
-
* @fires brut:submitinvalid Fired when the AJAX request initated by this returns a 422 and all logic around managing the reponse has completed
|
65
|
+
* @fires brut:submitok Fired when the AJAX request initated by this returns OK and all processing has completed. The detail will include the *parsed document* of the HTML returned in the response.
|
66
|
+
* @fires brut:submitinvalid Fired when the AJAX request initated by this returns a 422 and all logic around managing the reponse has completed. The detail will be null unless `no-server-side-error-parsing` is set, in which case it will be the parsed document of the HTML returned in the response.
|
49
67
|
*
|
50
68
|
* @example
|
51
69
|
* <form action="/widgets" method="post">
|
@@ -68,6 +86,7 @@ class AjaxSubmit extends BaseCustomElement {
|
|
68
86
|
"request-timeout",
|
69
87
|
"max-retry-attempts",
|
70
88
|
"log-request-errors",
|
89
|
+
"no-server-side-error-parsing",
|
71
90
|
]
|
72
91
|
|
73
92
|
#requestErrorLogger = () => {}
|
@@ -75,6 +94,12 @@ class AjaxSubmit extends BaseCustomElement {
|
|
75
94
|
#submittedLifetime = 2000
|
76
95
|
#requestTimeout = 5000
|
77
96
|
#maxRetryAttempts = 25
|
97
|
+
#serverSideErrorParsing = true
|
98
|
+
|
99
|
+
constructor() {
|
100
|
+
super()
|
101
|
+
this.domParser = new DOMParser()
|
102
|
+
}
|
78
103
|
|
79
104
|
submittedLifetimeChangedCallback({newValue}) {
|
80
105
|
const newValueAsInt = parseInt(newValue)
|
@@ -84,6 +109,10 @@ class AjaxSubmit extends BaseCustomElement {
|
|
84
109
|
this.#submittedLifetime = newValueAsInt
|
85
110
|
}
|
86
111
|
|
112
|
+
noServerSideErrorParsingChangedCallback({newValueAsBoolean}) {
|
113
|
+
this.#serverSideErrorParsing = !newValueAsBoolean
|
114
|
+
}
|
115
|
+
|
87
116
|
maxRetryAttemptsChangedCallback({newValue}) {
|
88
117
|
const num = parseInt(newValue)
|
89
118
|
if (isNaN(num)) {
|
@@ -187,7 +216,10 @@ class AjaxSubmit extends BaseCustomElement {
|
|
187
216
|
this.setAttribute("submitted",true)
|
188
217
|
|
189
218
|
setTimeout( () => this.removeAttribute("submitted"), this.#submittedLifetime )
|
190
|
-
|
219
|
+
response.text().then( (text) => {
|
220
|
+
const parsedDocument = this.domParser.parseFromString(text,"text/html")
|
221
|
+
this.dispatchEvent(new CustomEvent("brut:submitok", { detail: parsedDocument }))
|
222
|
+
})
|
191
223
|
}
|
192
224
|
else {
|
193
225
|
|
@@ -250,49 +282,53 @@ class AjaxSubmit extends BaseCustomElement {
|
|
250
282
|
#handleConstraintViolations(response) {
|
251
283
|
let resubmit = false
|
252
284
|
response.text().then( (text) => {
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
285
|
+
const parsedDocument = this.domParser.parseFromString(text,"text/html")
|
286
|
+
let event
|
287
|
+
if (this.#serverSideErrorParsing) {
|
288
|
+
event = new CustomEvent("brut:submitinvalid")
|
289
|
+
}
|
290
|
+
else {
|
291
|
+
event = new CustomEvent("brut:submitinvalid", { detail: parsedDocument })
|
292
|
+
}
|
293
|
+
this.dispatchEvent(event)
|
294
|
+
if (this.#serverSideErrorParsing) {
|
295
|
+
const constraintViolationNodes = parsedDocument.querySelectorAll(ConstraintViolationMessage.tagName)
|
296
|
+
try {
|
297
|
+
const inputsToMessages = ErrorMessagesForInput.mapInputsToErrorMessages(
|
298
|
+
constraintViolationNodes,
|
299
|
+
this.#requestErrorLogger
|
300
|
+
)
|
301
|
+
|
302
|
+
let inputToScrollToAfterReportingValidity
|
303
|
+
for (const [inputName, {input, messagesElement, errorMessages}] of Object.entries(inputsToMessages)) {
|
304
|
+
if (!inputToScrollToAfterReportingValidity) {
|
305
|
+
inputToScrollToAfterReportingValidity = input
|
306
|
+
}
|
307
|
+
messagesElement.clearServerSideMessages()
|
308
|
+
errorMessages.forEach( (element) => {
|
309
|
+
ConstraintViolationMessage.markServerSide(element)
|
310
|
+
messagesElement.appendChild(element)
|
311
|
+
})
|
312
|
+
this.#setCustomValidityThatClearsOnChange(input,errorMessages)
|
313
|
+
}
|
258
314
|
|
259
|
-
|
260
|
-
|
261
|
-
if (!inputToScrollToAfterReportingValidity) {
|
262
|
-
inputToScrollToAfterReportingValidity = input
|
315
|
+
if (inputToScrollToAfterReportingValidity) {
|
316
|
+
inputToScrollToAfterReportingValidity.scrollIntoView()
|
263
317
|
}
|
264
|
-
|
265
|
-
|
266
|
-
ConstraintViolationMessage.markServerSide(element)
|
267
|
-
messagesElement.appendChild(element)
|
268
|
-
})
|
269
|
-
this.#setCustomValidityThatClearsOnChange(input,errorMessages)
|
318
|
+
resubmit = false
|
319
|
+
this.removeAttribute("requesting")
|
270
320
|
}
|
271
|
-
|
272
|
-
|
273
|
-
|
321
|
+
catch (e) {
|
322
|
+
this.#requestErrorLogger("While parsing %s, got %s", text, e)
|
323
|
+
resubmit = true
|
324
|
+
}
|
325
|
+
if (resubmit) {
|
326
|
+
this.#submitFormThroughBrowser(form)
|
274
327
|
}
|
275
|
-
resubmit = false
|
276
|
-
this.removeAttribute("requesting")
|
277
|
-
this.dispatchEvent(new CustomEvent("brut:submitinvalid"))
|
278
|
-
}
|
279
|
-
catch (e) {
|
280
|
-
this.#requestErrorLogger("While parsing %s, got %s", text, e)
|
281
|
-
resubmit = true
|
282
|
-
}
|
283
|
-
if (resubmit) {
|
284
|
-
this.#submitFormThroughBrowser(form)
|
285
328
|
}
|
286
329
|
})
|
287
330
|
}
|
288
331
|
|
289
|
-
#errorMessagesFromServer(text) {
|
290
|
-
const parser = new DOMParser()
|
291
|
-
const fragment = parser.parseFromString(text,"text/html")
|
292
|
-
|
293
|
-
return fragment.querySelectorAll(ConstraintViolationMessage.tagName)
|
294
|
-
}
|
295
|
-
|
296
332
|
#setCustomValidityThatClearsOnChange(input,errorMessages) {
|
297
333
|
input.setCustomValidity(errorMessages[0].textContent)
|
298
334
|
input.reportValidity()
|
data/brut.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["David Bryant Copeland"]
|
9
9
|
spec.email = ["davec@thirdtank.com"]
|
10
10
|
|
11
|
-
spec.summary = %q{
|
12
|
-
spec.description = %q{
|
11
|
+
spec.summary = %q{Web Framework Built around Ruby, Web Standards, Simplicity, and Object-Orientation}
|
12
|
+
spec.description = %q{An opinionated web framework build on web standards}
|
13
13
|
spec.homepage = "https://naildrivin5.com"
|
14
14
|
|
15
15
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
data/brutrb.com/adrs.md
CHANGED
@@ -13,3 +13,4 @@ them, and mostly hold up.
|
|
13
13
|
* [Brut will not provider mailers, cloud storage, or background jobs](https://adrs.cloud/shared_adrs/padr_e7078d29f00710848ebbe43aea6094a6)
|
14
14
|
* [Requiring files should not execute code](https://adrs.cloud/shared_adrs/padr_03b5cf8e34bb7900a38f6de27e50adaa)
|
15
15
|
* [Phlex will be used for HTML templating](https://adrs.cloud/shared_adrs/padr_9529a5ada671254b5e5865b45abde92d)
|
16
|
+
* [RSpec is the default testing framework](https://adrs.cloud/shared_adrs/padr_724a65340d734970ea222593a4a2760f)
|
data/brutrb.com/bin/setup
CHANGED
@@ -11,6 +11,7 @@ The simplest way to use `mkbrut` is to use an existing [Docker image](https://hu
|
|
11
11
|
docker run \
|
12
12
|
-v "$PWD":"$PWD" \
|
13
13
|
-w "$PWD" \
|
14
|
+
-u $(id -u):$(id -g) \
|
14
15
|
-it \
|
15
16
|
thirdtank/mkbrut \
|
16
17
|
mkbrut my-new-app
|
@@ -34,6 +35,7 @@ For now:
|
|
34
35
|
docker run \
|
35
36
|
-v "$PWD":"$PWD" \
|
36
37
|
-w "$PWD" \
|
38
|
+
-u $(id -u):$(id -g) \
|
37
39
|
-it \
|
38
40
|
thirdtank/mkbrut \
|
39
41
|
mkbrut my-new-app
|
@@ -55,6 +57,7 @@ To create your app without the demo components:
|
|
55
57
|
docker run \
|
56
58
|
-v "$PWD":"$PWD" \
|
57
59
|
-w "$PWD" \
|
60
|
+
-u $(id -u):$(id -g) \
|
58
61
|
-it \
|
59
62
|
thirdtank/mkbrut \
|
60
63
|
mkbrut my-new-app --no-demo
|
data/brutrb.com/overview.md
CHANGED
@@ -20,6 +20,12 @@ more aestheticly pleasing form. You already need to know CSS, HTML, the Web
|
|
20
20
|
Platform, and SQL, so there's little to gain by requiring you to learn a different
|
21
21
|
way to use them.
|
22
22
|
|
23
|
+
## Yes, You Can Build a Blog in 15 Minutes
|
24
|
+
|
25
|
+
<iframe title="Make a Blog App in 15(ish) minutes With BrutRB - A New Ruby Web Framework" width="560" height="315" src="https://video.hardlimit.com/videos/embed/ae7EMhwjDq9kSH5dqQ9swV" frameborder="0" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe>
|
26
|
+
|
27
|
+
[Check out the source code](https://github.com/thirdtank/blog-demo) if you want to dive right in.
|
28
|
+
|
23
29
|
## Basic Elements
|
24
30
|
|
25
31
|
Brut organizes its code and behavior around four basic concepts:
|
data/brutrb.com/tutorial.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
A real tutorial will appear here eventually.
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
There are two apps you can check out in the mean time:
|
6
|
+
|
7
|
+
* [A 15-minute Blog](https://github.com/thirdtank/blog-demo) (see [demo video on how this is made](https://video.hardlimit.com/w/ae7EMhwjDq9kSH5dqQ9swV))
|
8
|
+
* [ADRs.cloud](https://github.com/thirdtank/adrs.cloud), which is a more realistic app that has
|
9
|
+
mulitple database tables, progressively-enhanced UI, and background jobs.
|
10
|
+
|
11
|
+
You can be running either of these locally in minutes as long as you have Docker installed.
|
data/docs/404.html
CHANGED
@@ -9,7 +9,7 @@
|
|
9
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.DyQLb4Ot.js"></script>
|
13
13
|
<link rel="icon" href="/favicon.ico">
|
14
14
|
<meta property="og:title" content="BrutRB Documentation">
|
15
15
|
<meta property="og:type" content="website">
|
@@ -20,7 +20,7 @@
|
|
20
20
|
</head>
|
21
21
|
<body>
|
22
22
|
<div id="app"></div>
|
23
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"
|
23
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"BxjHi9-8\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"DHh-NwKs\",\"configuration.md\":\"D8Wz3oJU\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"C5gXexJi\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"DRH2D2-O\",\"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\":\"DK5adCgM\",\"forms.md\":\"BRE85eju\",\"getting-started.md\":\"2ioiTe-B\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CVGl9xIO\",\"lsp.md\":\"Dn1rIiW0\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"overview.md\":\"DlKiRRG_\",\"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_migrations.md\":\"DPN3gQE3\",\"recipes_text-field-component.md\":\"H4wLAK0Z\",\"roadmap.md\":\"C6PRi0DX\",\"routes.md\":\"BD6y2i-f\",\"security.md\":\"C0G_AZR-\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"xl44xDos\",\"tutorial.md\":\"BIb7XT6j\",\"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\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"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>
|
24
24
|
|
25
25
|
</body>
|
26
26
|
</html>
|