brut 0.0.20 → 0.0.22
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 +24 -3
- data/.nvim.lua +1 -0
- data/Dockerfile.dx +12 -3
- data/Gemfile.lock +9 -7
- data/README.md +0 -7
- data/Rakefile +6 -4
- data/bin/dev +20 -0
- data/bin/docs +27 -0
- data/bin/setup +47 -1
- data/brut-css/.nvim.lua +1 -0
- data/brut-css/README.md +28 -0
- data/brut-css/bin/build +31 -0
- data/brut-css/bin/dev +1 -0
- data/brut-css/bin/docs +15 -0
- data/brut-css/bin/setup +5 -0
- data/brut-css/config/media-queries-all.css +15 -0
- data/brut-css/config/media-queries-minimal.css +5 -0
- data/brut-css/config/postcss.config.cjs +7 -0
- data/brut-css/config/pseudo-classes-all.css +9 -0
- data/brut-css/dx +1 -0
- data/brut-css/package-lock.json +3217 -0
- data/brut-css/package.json +36 -0
- data/brut-css/src/css/appearance.css +145 -0
- data/brut-css/src/css/border.css +522 -0
- data/brut-css/src/css/colors.css +3502 -0
- data/brut-css/src/css/dimensions.css +548 -0
- data/brut-css/src/css/flex.css +179 -0
- data/brut-css/src/css/index.css +13 -0
- data/brut-css/src/css/layout.css +120 -0
- data/brut-css/src/css/list.css +41 -0
- data/brut-css/src/css/positioning.css +354 -0
- data/brut-css/src/css/properties/colors.css +455 -0
- data/brut-css/src/css/properties/index.css +3 -0
- data/brut-css/src/css/properties/spacing.css +140 -0
- data/brut-css/src/css/properties/typography.css +224 -0
- data/brut-css/src/css/reset.css +107 -0
- data/brut-css/src/css/spacing.css +585 -0
- data/brut-css/src/css/typography.css +519 -0
- data/brut-css/src/css/utils.css +104 -0
- data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
- data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
- data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
- data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
- data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
- data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
- data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
- data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
- data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
- data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
- data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
- data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
- data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
- data/brut-css/src/docs/docs.css +98 -0
- data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
- data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
- data/brut-css/src/docs/includes/head.html.ejs +5 -0
- data/brut-css/src/docs/includes/nav.html.ejs +10 -0
- data/brut-css/src/docs/index.html.ejs +32 -0
- data/brut-css/src/docs/prism-twilight.min.css +1 -0
- data/brut-css/src/js/Logger.js +71 -0
- data/brut-css/src/js/build.js +111 -0
- data/brut-css/src/js/cli/CLIArgError.js +7 -0
- data/brut-css/src/js/cli/Debug.js +27 -0
- data/brut-css/src/js/cli/DocsDir.js +16 -0
- data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
- data/brut-css/src/js/cli/InputFile.js +31 -0
- data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
- data/brut-css/src/js/cli/OutputFile.js +22 -0
- data/brut-css/src/js/cli/ParsedArg.js +17 -0
- data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
- data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
- data/brut-css/src/js/cli.js +108 -0
- data/brut-css/src/js/docGenerator.js +467 -0
- data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
- data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
- data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
- data/brut-js/.projections.json +10 -0
- data/brut-js/README.md +118 -0
- data/brut-js/bin/build +10 -0
- data/brut-js/bin/ci +5 -0
- data/brut-js/bin/setup +5 -0
- data/brut-js/docs/README.md +8 -0
- data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
- data/brut-js/docs/jsdoc-theme/publish.js +692 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
- data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
- data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
- data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
- data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
- data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
- data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
- data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
- data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
- data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
- data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
- data/brut-js/docs/jsdoc.config.json +23 -0
- data/brut-js/docs/package-lock.json +343 -0
- data/brut-js/docs/package.json +7 -0
- data/brut-js/package-lock.json +2171 -0
- data/brut-js/package.json +32 -0
- data/brut-js/specs/AjaxSubmit.spec.js +256 -0
- data/brut-js/specs/Autosubmit.spec.js +127 -0
- data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
- data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
- data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
- data/brut-js/specs/CopyToClipboard.spec.js +35 -0
- data/brut-js/specs/Form.spec.js +181 -0
- data/brut-js/specs/I18nTranslation.spec.js +19 -0
- data/brut-js/specs/LocaleDetection.spec.js +22 -0
- data/brut-js/specs/Message.spec.js +15 -0
- data/brut-js/specs/SpecHelper.js +23 -0
- data/brut-js/specs/Tabs.spec.js +41 -0
- data/brut-js/specs/config/asset_metadata.json +7 -0
- data/brut-js/src/AjaxSubmit.js +384 -0
- data/brut-js/src/Autosubmit.js +63 -0
- data/brut-js/src/BaseCustomElement.js +261 -0
- data/brut-js/src/ConfirmSubmit.js +116 -0
- data/brut-js/src/ConfirmationDialog.js +143 -0
- data/brut-js/src/ConstraintViolationMessage.js +125 -0
- data/brut-js/src/ConstraintViolationMessages.js +98 -0
- data/brut-js/src/CopyToClipboard.js +96 -0
- data/brut-js/src/Form.js +151 -0
- data/brut-js/src/I18nTranslation.js +61 -0
- data/brut-js/src/LocaleDetection.js +117 -0
- data/brut-js/src/Logger.js +90 -0
- data/brut-js/src/Message.js +56 -0
- data/brut-js/src/RichString.js +113 -0
- data/brut-js/src/Tabs.js +168 -0
- data/brut-js/src/Tracing.js +247 -0
- data/brut-js/src/appForTestingOnly.js +15 -0
- data/brut-js/src/index.js +130 -0
- data/brut-js/src/testing/AssetMetadata.js +35 -0
- data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
- data/brut-js/src/testing/CustomElementTest.js +235 -0
- data/brut-js/src/testing/DOMCreator.js +45 -0
- data/brut-js/src/testing/index.js +48 -0
- data/brutrb.com/.vitepress/config.mjs +106 -0
- data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
- data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/index.js +18 -0
- data/brutrb.com/.vitepress/theme/style.css +149 -0
- data/brutrb.com/ai.md +68 -0
- data/brutrb.com/assets.md +138 -0
- data/brutrb.com/bin/build +5 -0
- data/brutrb.com/bin/deploy +7 -0
- data/brutrb.com/bin/dev +5 -0
- data/brutrb.com/bin/setup +5 -0
- data/brutrb.com/brut-js.md +117 -0
- data/brutrb.com/business-logic.md +55 -0
- data/brutrb.com/cli.md +278 -0
- data/brutrb.com/components.md +243 -0
- data/brutrb.com/configuration.md +257 -0
- data/brutrb.com/css.md +103 -0
- data/brutrb.com/custom-element-tests.md +149 -0
- data/brutrb.com/database-access.md +201 -0
- data/brutrb.com/database-schema.md +312 -0
- data/brutrb.com/deployment.md +66 -0
- data/brutrb.com/dev-environment.md +179 -0
- data/brutrb.com/doc-conventions.md +39 -0
- data/brutrb.com/end-to-end-tests.md +174 -0
- data/brutrb.com/flash-and-session.md +224 -0
- data/brutrb.com/forms.md +866 -0
- data/brutrb.com/getting-started.md +66 -0
- data/brutrb.com/handlers.md +153 -0
- data/brutrb.com/hooks.md +178 -0
- data/brutrb.com/i18n.md +188 -0
- data/brutrb.com/images/Makefile +10 -0
- data/brutrb.com/images/dev-env-overview.dot +54 -0
- data/brutrb.com/images/dev-env-overview.png +0 -0
- data/brutrb.com/images/dev-env-protocol.dot +37 -0
- data/brutrb.com/images/dev-env-protocol.png +0 -0
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/images/overview.graffle +0 -0
- data/brutrb.com/images/overview.png +0 -0
- data/brutrb.com/images/spa.dot +19 -0
- data/brutrb.com/images/spa.png +0 -0
- data/brutrb.com/images/workspace-protocol.dot +44 -0
- data/brutrb.com/images/workspace-protocol.png +0 -0
- data/brutrb.com/index.md +36 -0
- data/brutrb.com/instrumentation.md +183 -0
- data/brutrb.com/javascript.md +122 -0
- data/brutrb.com/jobs.md +14 -0
- data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
- data/brutrb.com/markdown-examples.md +85 -0
- data/brutrb.com/middleware.md +80 -0
- data/brutrb.com/not-released.md +5 -0
- data/brutrb.com/overview.md +404 -0
- data/brutrb.com/package-lock.json +2404 -0
- data/brutrb.com/package.json +11 -0
- data/brutrb.com/pages.md +378 -0
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/brutrb.com/routes.md +215 -0
- data/brutrb.com/security.md +105 -0
- data/brutrb.com/seed-data.md +63 -0
- data/brutrb.com/space-time-continuum.md +85 -0
- data/brutrb.com/tutorial.md +3 -0
- data/brutrb.com/unit-tests.md +148 -0
- data/docker-compose.dx.yml +6 -3
- data/docs/404.html +21 -0
- data/docs/CNAME +1 -0
- data/docs/ai.html +24 -0
- data/docs/api/Brut/BackEnd/SeedData.html +493 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
- data/docs/api/Brut/BackEnd/Validators.html +128 -0
- data/docs/api/Brut/BackEnd.html +132 -0
- data/docs/api/Brut/CLI/App.html +1576 -0
- data/docs/api/Brut/CLI/AppRunner.html +491 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
- data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
- data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB.html +183 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
- data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
- data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
- data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
- data/docs/api/Brut/CLI/Apps/Test.html +253 -0
- data/docs/api/Brut/CLI/Apps.html +125 -0
- data/docs/api/Brut/CLI/Command.html +2342 -0
- data/docs/api/Brut/CLI/Error.html +139 -0
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
- data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
- data/docs/api/Brut/CLI/Executor.html +430 -0
- data/docs/api/Brut/CLI/InvalidOption.html +245 -0
- data/docs/api/Brut/CLI/Options.html +753 -0
- data/docs/api/Brut/CLI/Output.html +699 -0
- data/docs/api/Brut/CLI/SystemExecError.html +451 -0
- data/docs/api/Brut/CLI.html +263 -0
- data/docs/api/Brut/FactoryBot.html +225 -0
- data/docs/api/Brut/Framework/App.html +1097 -0
- data/docs/api/Brut/Framework/Config.html +1045 -0
- data/docs/api/Brut/Framework/Container.html +1379 -0
- data/docs/api/Brut/Framework/Error.html +140 -0
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
- data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
- data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
- data/docs/api/Brut/Framework/Errors.html +328 -0
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
- data/docs/api/Brut/Framework/MCP.html +861 -0
- data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
- data/docs/api/Brut/Framework.html +129 -0
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
- data/docs/api/Brut/FrontEnd/Component.html +365 -0
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
- data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
- data/docs/api/Brut/FrontEnd/Components.html +135 -0
- data/docs/api/Brut/FrontEnd/Download.html +467 -0
- data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
- data/docs/api/Brut/FrontEnd/Form.html +1157 -0
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms.html +127 -0
- data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
- data/docs/api/Brut/FrontEnd/Handler.html +442 -0
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
- data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
- data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
- data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
- data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
- data/docs/api/Brut/FrontEnd/Layout.html +318 -0
- data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
- data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
- data/docs/api/Brut/FrontEnd/Page.html +773 -0
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
- data/docs/api/Brut/FrontEnd/Pages.html +125 -0
- data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
- data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
- data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
- data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
- data/docs/api/Brut/FrontEnd/Routing.html +927 -0
- data/docs/api/Brut/FrontEnd/Session.html +1195 -0
- data/docs/api/Brut/FrontEnd.html +134 -0
- data/docs/api/Brut/I18n/BaseMethods.html +931 -0
- data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
- data/docs/api/Brut/I18n/ForCLI.html +302 -0
- data/docs/api/Brut/I18n/ForHTML.html +296 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
- data/docs/api/Brut/I18n.html +127 -0
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
- data/docs/api/Brut/Instrumentation.html +126 -0
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
- data/docs/api/Brut/SinatraHelpers.html +281 -0
- data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
- data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
- data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
- data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
- data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
- data/docs/api/Brut/SpecSupport.html +129 -0
- data/docs/api/Brut.html +225 -0
- data/docs/api/Clock.html +603 -0
- data/docs/api/RichString.html +968 -0
- data/docs/api/SemanticLogger/Appender/Async.html +219 -0
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
- data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
- data/docs/api/Sequel/Extensions.html +117 -0
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
- data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
- data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
- data/docs/api/Sequel/Plugins/FindBang.html +125 -0
- data/docs/api/Sequel/Plugins.html +117 -0
- data/docs/api/Sequel.html +117 -0
- data/docs/api/_index.html +1553 -0
- data/docs/api/class_list.html +54 -0
- data/docs/api/css/common.css +1 -0
- data/docs/api/css/full_list.css +58 -0
- data/docs/api/css/style.css +503 -0
- data/docs/api/file.README.html +127 -0
- data/docs/api/file_list.html +59 -0
- data/docs/api/frames.html +22 -0
- data/docs/api/index.html +127 -0
- data/docs/api/js/app.js +344 -0
- data/docs/api/js/full_list.js +242 -0
- data/docs/api/js/jquery.js +4 -0
- data/docs/api/method_list.html +3998 -0
- data/docs/api/top-level-namespace.html +112 -0
- data/docs/assets/ai.md.tZrjP9im.js +1 -0
- data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
- data/docs/assets/app.D_yaTITQ.js +1 -0
- data/docs/assets/assets.md.D3wunzLx.js +19 -0
- data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
- data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
- data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
- data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
- data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
- data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
- data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
- data/docs/assets/cli.md.RmeA2b0i.js +127 -0
- data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
- data/docs/assets/components.md.eCttGlN-.js +104 -0
- data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
- data/docs/assets/configuration.md.BRriU0cL.js +78 -0
- data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
- data/docs/assets/css.md.DJgj2clw.js +21 -0
- data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
- data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
- data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
- data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
- data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
- data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
- data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
- data/docs/assets/handlers.md.089DVD3v.js +69 -0
- data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
- data/docs/assets/hooks.md.C4-moMny.js +80 -0
- data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
- data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
- data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
- data/docs/assets/index.md.B28EwVpq.js +1 -0
- data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
- data/docs/assets/javascript.md.GWbhRS51.js +31 -0
- data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
- data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
- data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
- data/docs/assets/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.CDalkuxV.js +133 -0
- data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
- data/docs/assets/pages.md.BE3kfOc5.js +122 -0
- data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
- data/docs/assets/routes.md.BMM7peut.js +29 -0
- data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
- data/docs/assets/security.md.C668yXCi.js +1 -0
- data/docs/assets/security.md.C668yXCi.lean.js +1 -0
- data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
- data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
- data/docs/assets/spa.qejUdp-5.png +0 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
- data/docs/assets/style.D73IYGCX.css +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
- data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
- data/docs/assets.html +42 -0
- data/docs/brut-css/brut.css +1 -0
- data/docs/brut-css/brut.max.css +22372 -0
- data/docs/brut-css/classes/appearances.html +783 -0
- data/docs/brut-css/classes/background-colors.html +3529 -0
- data/docs/brut-css/classes/border-colors.html +3529 -0
- data/docs/brut-css/classes/borders.html +2293 -0
- data/docs/brut-css/classes/dimensions.html +2581 -0
- data/docs/brut-css/classes/flex.html +917 -0
- data/docs/brut-css/classes/foreground-colors.html +3261 -0
- data/docs/brut-css/classes/junk-drawer.html +431 -0
- data/docs/brut-css/classes/layout.html +668 -0
- data/docs/brut-css/classes/lists.html +331 -0
- data/docs/brut-css/classes/positioning.html +1751 -0
- data/docs/brut-css/classes/spacings.html +2633 -0
- data/docs/brut-css/classes/typography.html +2206 -0
- data/docs/brut-css/customization/advanced-configuration.html +204 -0
- data/docs/brut-css/customization/breakpoints.html +227 -0
- data/docs/brut-css/customization/design-system.html +197 -0
- data/docs/brut-css/customization/pseudo-classes.html +228 -0
- data/docs/brut-css/docs.css +98 -0
- data/docs/brut-css/getting-started/core-concepts.html +234 -0
- data/docs/brut-css/getting-started/installation.html +190 -0
- data/docs/brut-css/getting-started/overview.html +210 -0
- data/docs/brut-css/getting-started/simple-example.html +285 -0
- data/docs/brut-css/index.html +193 -0
- data/docs/brut-css/prism-twilight.min.css +1 -0
- data/docs/brut-css/properties/colors.html +1548 -0
- data/docs/brut-css/properties/spacings.html +614 -0
- data/docs/brut-css/properties/typography.html +777 -0
- data/docs/brut-js/api/AjaxSubmit.html +374 -0
- data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
- data/docs/brut-js/api/Autosubmit.html +192 -0
- data/docs/brut-js/api/Autosubmit.js.html +114 -0
- data/docs/brut-js/api/BaseCustomElement.html +1091 -0
- data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
- data/docs/brut-js/api/BrutCustomElements.html +172 -0
- data/docs/brut-js/api/BufferedLogger.html +173 -0
- data/docs/brut-js/api/ConfirmSubmit.html +278 -0
- data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
- data/docs/brut-js/api/ConfirmationDialog.html +425 -0
- data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
- data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
- data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
- data/docs/brut-js/api/CopyToClipboard.html +345 -0
- data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
- data/docs/brut-js/api/Form.html +294 -0
- data/docs/brut-js/api/Form.js.html +202 -0
- data/docs/brut-js/api/I18nTranslation.html +409 -0
- data/docs/brut-js/api/I18nTranslation.js.html +112 -0
- data/docs/brut-js/api/LocaleDetection.html +312 -0
- data/docs/brut-js/api/LocaleDetection.js.html +168 -0
- data/docs/brut-js/api/Logger.html +702 -0
- data/docs/brut-js/api/Logger.js.html +141 -0
- data/docs/brut-js/api/Message.html +238 -0
- data/docs/brut-js/api/Message.js.html +107 -0
- data/docs/brut-js/api/PrefixedLogger.html +369 -0
- data/docs/brut-js/api/RichString.html +1049 -0
- data/docs/brut-js/api/RichString.js.html +164 -0
- data/docs/brut-js/api/Tabs.html +295 -0
- data/docs/brut-js/api/Tabs.js.html +219 -0
- data/docs/brut-js/api/Tracing.html +277 -0
- data/docs/brut-js/api/Tracing.js.html +298 -0
- data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
- data/docs/brut-js/api/external-Performance.html +138 -0
- data/docs/brut-js/api/external-Promise.html +138 -0
- data/docs/brut-js/api/external-ValidityState.html +138 -0
- data/docs/brut-js/api/external-Window.html +233 -0
- data/docs/brut-js/api/external-fetch.html +138 -0
- data/docs/brut-js/api/global.html +400 -0
- data/docs/brut-js/api/index.html +168 -0
- data/docs/brut-js/api/index.js.html +181 -0
- data/docs/brut-js/api/module-testing.html +383 -0
- data/docs/brut-js/api/scripts/linenumber.js +25 -0
- data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
- data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
- data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
- data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
- data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
- data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
- data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
- data/docs/brut-js/api/testing.DOMCreator.html +171 -0
- data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
- data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
- data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
- data/docs/brut-js/api/testing_index.js.html +99 -0
- data/docs/brut-js.html +35 -0
- data/docs/business-logic.html +24 -0
- data/docs/cli.html +150 -0
- data/docs/components.html +127 -0
- data/docs/configuration.html +101 -0
- data/docs/css.html +44 -0
- data/docs/custom-element-tests.html +92 -0
- data/docs/database-access.html +86 -0
- data/docs/database-schema.html +86 -0
- data/docs/deployment.html +24 -0
- data/docs/dev-environment.html +34 -0
- data/docs/doc-conventions.html +24 -0
- data/docs/end-to-end-tests.html +49 -0
- data/docs/flash-and-session.html +116 -0
- data/docs/forms.html +402 -0
- data/docs/getting-started.html +25 -0
- data/docs/handlers.html +92 -0
- data/docs/hashmap.json +1 -0
- data/docs/hooks.html +103 -0
- data/docs/i18n.html +46 -0
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/index.html +24 -0
- data/docs/instrumentation.html +58 -0
- data/docs/javascript.html +54 -0
- data/docs/jobs.html +24 -0
- data/docs/keyword-injection.html +48 -0
- data/docs/markdown-examples.html +56 -0
- data/docs/middleware.html +43 -0
- data/docs/not-released.html +24 -0
- data/docs/overview.html +156 -0
- data/docs/pages.html +145 -0
- data/docs/routes.html +52 -0
- data/docs/security.html +24 -0
- data/docs/seed-data.html +37 -0
- data/docs/space-time-continuum.html +24 -0
- data/docs/tutorial.html +24 -0
- data/docs/unit-tests.html +36 -0
- data/docs/vp-icons.css +1 -0
- data/lib/brut/back_end/seed_data.rb +19 -2
- data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
- data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
- data/lib/brut/back_end/sidekiq.rb +2 -1
- data/lib/brut/back_end/validator.rb +5 -1
- data/lib/brut/back_end.rb +4 -2
- data/lib/brut/cli/app_runner.rb +1 -1
- data/lib/brut/cli/apps/test.rb +5 -0
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/factory_bot.rb +0 -5
- data/lib/brut/framework/app.rb +70 -5
- data/lib/brut/framework/config.rb +5 -3
- data/lib/brut/framework/container.rb +3 -2
- data/lib/brut/framework/errors.rb +12 -4
- data/lib/brut/framework/mcp.rb +58 -1
- data/lib/brut/framework/project_environment.rb +6 -2
- data/lib/brut/framework.rb +1 -1
- data/lib/brut/front_end/component.rb +69 -71
- data/lib/brut/front_end/components/constraint_violations.rb +1 -4
- data/lib/brut/front_end/components/form_tag.rb +1 -1
- data/lib/brut/front_end/components/input.rb +3 -3
- data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
- data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
- data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
- data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
- data/lib/brut/front_end/components/time_tag.rb +2 -1
- data/lib/brut/front_end/form.rb +4 -4
- data/lib/brut/front_end/forms/input.rb +2 -1
- data/lib/brut/front_end/forms/input_definition.rb +5 -2
- data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
- data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
- data/lib/brut/front_end/forms/select_input.rb +2 -4
- data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
- data/lib/brut/front_end/handler.rb +28 -26
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
- data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
- data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
- data/lib/brut/front_end/layout.rb +16 -0
- data/lib/brut/front_end/page.rb +52 -29
- data/lib/brut/front_end/request_context.rb +3 -2
- data/lib/brut/front_end/routing.rb +5 -1
- data/lib/brut/front_end.rb +4 -13
- data/lib/brut/i18n/base_methods.rb +167 -79
- data/lib/brut/i18n/for_back_end.rb +4 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +32 -4
- data/lib/brut/i18n/http_accept_language.rb +47 -0
- data/lib/brut/instrumentation/open_telemetry.rb +36 -1
- data/lib/brut/instrumentation.rb +3 -5
- data/lib/brut/sinatra_helpers.rb +11 -3
- data/lib/brut/spec_support/component_support.rb +30 -16
- data/lib/brut/spec_support/e2e_support.rb +1 -1
- data/lib/brut/spec_support/e2e_test_server.rb +3 -0
- data/lib/brut/spec_support/general_support.rb +3 -0
- data/lib/brut/spec_support/handler_support.rb +6 -1
- data/lib/brut/spec_support/matcher.rb +1 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
- data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
- data/lib/brut/spec_support.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +5 -4
- data/lib/sequel/extensions/brut_migrations.rb +1 -1
- metadata +648 -13
- data/doc-src/architecture.md +0 -102
- data/doc-src/assets.md +0 -98
- data/doc-src/forms.md +0 -214
- data/doc-src/handlers.md +0 -83
- data/doc-src/javascript.md +0 -265
- data/doc-src/pages.md +0 -210
- data/doc-src/route-hooks.md +0 -59
- data/lib/brut/front_end/components/inputs/select.rb +0 -117
data/brutrb.com/cli.md
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
# CLI and Tasks
|
2
|
+
|
3
|
+
Your app will likely need command-line or non-browser-based tasks. Outside of standard needs like running a dev server or managing the database, you will have app-specific needs, like transforming data or performing one-time bulk operations.
|
4
|
+
|
5
|
+
In Brut, these are done as Ruby CLI apps, powered by the standard library's `OptionParser`.
|
6
|
+
|
7
|
+
## Overview
|
8
|
+
|
9
|
+
The various commands installed with your Brut app in `bin/` are powered by Brut's CLI support. You can use this support to create your own tasks.
|
10
|
+
|
11
|
+
The main feature this provides is a consistent startup of your app's internals and configuration. This startup is almost exactly the same as the web app, so you can safely rely on connections to the database, the ability to queue jobs, or the behavior of your business logic. There's just no web server and no front-end.
|
12
|
+
|
13
|
+
### Brut CLI User Interface
|
14
|
+
|
15
|
+
All Brut CLI apps produce a user interface that should be familiar if you've used CLI apps that support subcommands, like `git`:
|
16
|
+
|
17
|
+
```
|
18
|
+
> git status
|
19
|
+
> git checkout
|
20
|
+
> git commit
|
21
|
+
```
|
22
|
+
|
23
|
+
Each Brut CLI invocation has six parts:
|
24
|
+
|
25
|
+
* The executable, e.g. `bin/db`
|
26
|
+
* *Global Options* which are strings that start witih one or two
|
27
|
+
dashes and control the behavior of the entire app, e.g. `--log-level=debug` These are optional and there can be any number of them.
|
28
|
+
* The subcommand, which is a single string indicating what actual
|
29
|
+
function to perform, e.g. `rebuild` in `bin/db rebuild`
|
30
|
+
* *Command Options* which are just like global options, but they
|
31
|
+
come *after* the command and apply only to that command
|
32
|
+
* Arguments, which are any strings left over after the *command
|
33
|
+
options* are parsed.
|
34
|
+
|
35
|
+
All of this is powered by Ruby's `OptionParser`, which results in a canonical, UNIX-like UI.
|
36
|
+
|
37
|
+
```
|
38
|
+
> bin/my_cli --global-option list --command-option arg1 arg2
|
39
|
+
\----+---/ \-------+-----/ \-+/ \--------+-----/ \---+---/
|
40
|
+
| | | | |
|
41
|
+
Executable | | | |
|
42
|
+
| | | |
|
43
|
+
Command | | |
|
44
|
+
Options | |
|
45
|
+
| | |
|
46
|
+
Subcommand | |
|
47
|
+
| |
|
48
|
+
Command |
|
49
|
+
Options |
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Arguments
|
53
|
+
|
54
|
+
```
|
55
|
+
|
56
|
+
Brut CLI apps all respond to `-h` and `--help` to view the list of subcommands, global options, and any environment variables that affect the behavior.
|
57
|
+
|
58
|
+
```
|
59
|
+
> bin/db -h
|
60
|
+
bin/db [global options] commands [command options] [args]
|
61
|
+
|
62
|
+
Manage your database in development, test, and production
|
63
|
+
|
64
|
+
GLOBAL OPTIONS
|
65
|
+
|
66
|
+
-h, --help Get help
|
67
|
+
--log-level=LEVEL Set log level. Allowed values: debug, info, warn, error, fatal. Default 'fatal'
|
68
|
+
--verbose Set log level to 'debug', which will produce maximum output
|
69
|
+
|
70
|
+
ENVIRONMENT VARIABLES
|
71
|
+
|
72
|
+
BRUT_CLI_RAISE_ON_ERROR - if set, shows backtrace on errors
|
73
|
+
LOG_LEVEL - log level if --log-level or --verbose is omitted
|
74
|
+
|
75
|
+
|
76
|
+
COMMANDS
|
77
|
+
|
78
|
+
help - Get help on a command
|
79
|
+
create - Create the database if it does not exist
|
80
|
+
drop - Drop the database if it exists
|
81
|
+
migrate - Apply any outstanding migrations to the database
|
82
|
+
new_migration - Create a new migration file
|
83
|
+
rebuild - Drop, re-create, and run migrations, effecitvely rebuilding the entire database
|
84
|
+
seed - Load seed data into the database
|
85
|
+
status - Check the status of the database and migrations
|
86
|
+
```
|
87
|
+
|
88
|
+
Brut CLI apps also support the subcommand `help` which will show help on a given subcommand, including the command options and arguments.
|
89
|
+
|
90
|
+
```
|
91
|
+
> bin/db help rebuild
|
92
|
+
Usage: bin/db [global options] rebuild [command options]
|
93
|
+
|
94
|
+
Drop, re-create, and run migrations, effecitvely rebuilding the entire database
|
95
|
+
|
96
|
+
GLOBAL OPTIONS
|
97
|
+
|
98
|
+
-h, --help Get help
|
99
|
+
--log-level=LEVEL Set log level. Allowed values: debug, info, warn, error, fatal. Default 'fatal'
|
100
|
+
--verbose Set log level to 'debug', which will produce maximum output
|
101
|
+
|
102
|
+
ENVIRONMENT VARIABLES
|
103
|
+
|
104
|
+
BRUT_CLI_RAISE_ON_ERROR - if set, shows backtrace on errors
|
105
|
+
LOG_LEVEL - log level if --log-level or --verbose is omitted
|
106
|
+
RACK_ENV - default project environment when --env is omitted
|
107
|
+
|
108
|
+
|
109
|
+
COMMAND OPTIONS
|
110
|
+
|
111
|
+
--env=ENVIRONMENT Project environment (default 'development')
|
112
|
+
```
|
113
|
+
|
114
|
+
All of this means that the bulk of CLI-specific code you will write is specifying these options and documentation, then deferring to your business logic.
|
115
|
+
|
116
|
+
### Basic CLI
|
117
|
+
|
118
|
+
Every CLI app is a class that extends `Brut::CLI::App`. This class should contain one inner class for each subcommand. Those classes should extend `Brut::CLI::App`.
|
119
|
+
|
120
|
+
Inside your `Brut::CLI::App` class, you can call a few class methods to declare aspects of the UI. In particular, `opts` returns the `OptionParser` in play that you can use to declare global options. Unlike `OptionParser`'s `on` method, Brut's does not require providing a block. Brut will store the runtime options in a hash (see below).
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class MyAppCLI < Brut::CLI::App
|
124
|
+
description "My awesome command line app"
|
125
|
+
|
126
|
+
opts.on("--dry-run", "Only show what would happen; don't change anything")
|
127
|
+
opts.on("--status STATUS", "Set the status you'd like to see")
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
This code means your app's global options are `--dry-run`, which will not accept an argument, and `--status` which *must* be given an argument. The arguments to `on` are the same as those for Ruby's `OptionParser`.
|
132
|
+
|
133
|
+
Declaring subcommands provides a similar API. Let's say our app has a "status" subcommand, and a "run" subcommand.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class MyAppCLI < Brut::CLI::App
|
137
|
+
description "My awesome command line app"
|
138
|
+
|
139
|
+
opts.on("--dry-run", "Only show what would happen; don't change anything")
|
140
|
+
opts.on("--status STATUS", "Set the status you'd like to see")
|
141
|
+
|
142
|
+
class Status < Brut::CLI::Command
|
143
|
+
description "Show the status"
|
144
|
+
args "files to get the status of"
|
145
|
+
opts.on("-l", "--long", "Show long-format")
|
146
|
+
end
|
147
|
+
|
148
|
+
class Run < Brut::CLI::Command
|
149
|
+
description "Run any outstanding tasks"
|
150
|
+
opts.on("--exit-status STATUS", "Exit status on success")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
This enables commands like `bin/my_app status -l foo.txt bar.rb` or `bin/my_app status --exit-status=3`.
|
156
|
+
|
157
|
+
The names are derived from the class name. You can override them by using `command_name` inside a command class.
|
158
|
+
|
159
|
+
```ruby {2}
|
160
|
+
class Run < Brut::CLI::Command
|
161
|
+
command_name "exec"
|
162
|
+
|
163
|
+
# ...
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
The only thing left is to specify what happens for each subcommand. To do that, implement `execute`.
|
168
|
+
|
169
|
+
```ruby {6-8,15-17}
|
170
|
+
class Status < Brut::CLI::Command
|
171
|
+
description "Show the status"
|
172
|
+
args "files to get the status of"
|
173
|
+
opts.on("-l", "--long", "Show long-format")
|
174
|
+
|
175
|
+
def execute
|
176
|
+
# ...
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class Run < Brut::CLI::Command
|
181
|
+
description "Run any outstanding tasks"
|
182
|
+
opts.on("--exit-status STATUS", "Exit status on success")
|
183
|
+
|
184
|
+
def execute
|
185
|
+
# ...
|
186
|
+
end
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
### Implementing `execute`
|
191
|
+
|
192
|
+
Once `execute` is called, your app's internals will have been setup and bootstrapped. That means all you data models can access the database, and any other setup will have ocurred. Generally, `execute` can then have whatever code makes sense.
|
193
|
+
|
194
|
+
That said, `execute` has access to a few values to understand the command line invocation and to support testing.
|
195
|
+
|
196
|
+
* `#options` - the command options passed on the command line, as
|
197
|
+
a `Brut::CLI::Options`
|
198
|
+
* `#global_options` - the global options passed on the command line, as a `Brut::CLI::Options`
|
199
|
+
* `#args` - the args passed on the command line, as an array of
|
200
|
+
strings
|
201
|
+
* `#out` - an IO you should use to print messages to the standard out.
|
202
|
+
* `#err` - on IO you should use to print messages to the standard error.
|
203
|
+
* `#system!` - the method you should use to spawn child processes.
|
204
|
+
This is preferable to `Kernel.system` because the command executed will be logged, and your app will raise if the command fails. This makes it more straightfoward to safely script other command line invocations.
|
205
|
+
|
206
|
+
### Advanced Options
|
207
|
+
|
208
|
+
The API documentation will show you other options for creating your CLI UI, but there are a few aspects to highlight.
|
209
|
+
|
210
|
+
* Calling `configure_only!` in your app, will run your app's
|
211
|
+
subcommands without starting up Brut. This is generally not needed, but can be useful if you want to do basic scripting without worrying about connecting to the database.
|
212
|
+
* `default_command` can be used to specify the command to run if
|
213
|
+
none is given. `bin/test` uses this to run `bin/test run`.
|
214
|
+
* `requires_project_env` can be used at the app or command level
|
215
|
+
to indicate that `--env` is accepted on the command line to set the project environment for the code to run in. Omitting this means that the actual project env used when the app runs is undefined.
|
216
|
+
* Use `env_var` to document environment variables your app will
|
217
|
+
use that affect its behavior.
|
218
|
+
|
219
|
+
### The file in `bin`
|
220
|
+
|
221
|
+
Currently, Brut doesn't provide a way to create this file, but it's relatively straightforward. It's almost entirely boilerplate except for your class:
|
222
|
+
|
223
|
+
```ruby {14}
|
224
|
+
#!/usr/bin/env ruby
|
225
|
+
|
226
|
+
require "bundler"
|
227
|
+
Bundler.require
|
228
|
+
require "pathname"
|
229
|
+
|
230
|
+
require "brut/cli"
|
231
|
+
|
232
|
+
APP_PATH = File.join(File.dirname($0),"..","app","src")
|
233
|
+
$: << APP_PATH
|
234
|
+
require "cli/my_app"
|
235
|
+
|
236
|
+
exit Brut::CLI.app(
|
237
|
+
MyAppCLI,
|
238
|
+
project_root: Pathname($0).dirname / ".."
|
239
|
+
)
|
240
|
+
```
|
241
|
+
|
242
|
+
## Testing
|
243
|
+
|
244
|
+
Depending on how your CLI is impelmented, testing it may not be that beneficial. If `execute` simply defers to your back-end, your tests of that back-end will generally suffice.
|
245
|
+
|
246
|
+
That said, your command classes are normal Ruby classes, so you can test them in a conventional way.
|
247
|
+
|
248
|
+
The initalizer of each command class looks like so:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
def initialize(
|
252
|
+
command_options:,
|
253
|
+
global_options:,
|
254
|
+
args:,
|
255
|
+
out:,
|
256
|
+
err:,
|
257
|
+
executor:
|
258
|
+
)
|
259
|
+
```
|
260
|
+
|
261
|
+
`command_options` and `global_options` accept a `Brut::CLI::Options`, which can be created with a Hash to represent the parsed command line options.
|
262
|
+
|
263
|
+
`args` is a list of Strings. `out` and `err` are IOs, so you can use the standard library's `StringIO` to both capture the output and supress it from your test's standard output/error.
|
264
|
+
|
265
|
+
`executor` is a `Brut::CLI::Executor`, which is a wrapper around the standard library's `Open3`. You can mock this to set expectations on what child processes are launched and how they behave.
|
266
|
+
|
267
|
+
## Recommended Practices
|
268
|
+
|
269
|
+
`execute` should defer to classes in your back-end, ideally a single method of a single class. Excessive logic or UI in your CLI will be hard to test and maintain.
|
270
|
+
|
271
|
+
|
272
|
+
## Technical Notes
|
273
|
+
|
274
|
+
> [!IMPORTANT]
|
275
|
+
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
276
|
+
> internals, the source code is always more correct.
|
277
|
+
|
278
|
+
_Last Updated May 9, 2025_
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# Components
|
2
|
+
|
3
|
+
Components in Brut are Phlex Components: a class that can hold data and use that to generate HTML. Components are the primary way you achieve re-use of markup or view logic.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
`Brut::FrontEnd::Component` inherits from `Phlex::HTML`, which means that to create a component you must do three things:
|
8
|
+
|
9
|
+
1. Create a class in `app/src/front_end/components` that inherites from `AppComponent` (which is part of your app and inherits from `Brut::FrontEnd::Component`)
|
10
|
+
2. Implement an initializer that receives anything your component needs to do its job. It is recommended (but not required) that your initializer use only keyword arguments.
|
11
|
+
3. Implement `view_template` in which you make calls to Phlex' API.
|
12
|
+
|
13
|
+
### Simple Component
|
14
|
+
|
15
|
+
For example, suppose you want a re-usable button component whose HTML would look like so:
|
16
|
+
|
17
|
+
```html
|
18
|
+
<button class="button button__small button__red">
|
19
|
+
Delete Files
|
20
|
+
</button>
|
21
|
+
```
|
22
|
+
|
23
|
+
Your button can be large (the default) or small, and could be gray (default), green, or red. Your button could also have an optional `formaction` attribute. Let's also suppose the label could be a string or embedded HTML.
|
24
|
+
|
25
|
+
Your constructor would need to accept the size, color, label, and formaction.
|
26
|
+
|
27
|
+
You can create a scaffold via `bin/scaffold`:
|
28
|
+
|
29
|
+
```
|
30
|
+
> bin/scaffold component Button
|
31
|
+
```
|
32
|
+
|
33
|
+
You'll first implement the initializer, like so:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# app/src/front_end/components/button.rb
|
37
|
+
class Button < AppComponent
|
38
|
+
def initialize(
|
39
|
+
size: :large,
|
40
|
+
color: :gray,
|
41
|
+
formaction: nil,
|
42
|
+
label: :use_block
|
43
|
+
)
|
44
|
+
@size = size
|
45
|
+
@color = color
|
46
|
+
@formaction = formaction
|
47
|
+
@label = label
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
This initializer is rather simplistic. You may want to validate the values here to prevent the construction of an invalid component.
|
53
|
+
|
54
|
+
Now, implement `view_template`. This method will receive a block if one is given when the component is used. We'll see an example in a minute.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# app/src/front_end/components/button.rb
|
58
|
+
class Button < AppComponent
|
59
|
+
|
60
|
+
# ...
|
61
|
+
|
62
|
+
def view_template
|
63
|
+
attributes = {
|
64
|
+
class: [
|
65
|
+
"button",
|
66
|
+
"button__#{@size}",
|
67
|
+
"button__#@{color}",
|
68
|
+
],
|
69
|
+
formaction: @formaction,
|
70
|
+
}
|
71
|
+
|
72
|
+
button(**attributes) do
|
73
|
+
if @label == :use_block
|
74
|
+
yield
|
75
|
+
else
|
76
|
+
@label
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
If you've never used Phlex before, it's refreshingly straightforward:
|
84
|
+
|
85
|
+
* There's a method for each HTML element.
|
86
|
+
* The method's parameters produce attributes in the HTML that is generated.
|
87
|
+
* If a parameter's value is an array (like `class:`), the values are joined with strings to form the atttribute's value in HTML.
|
88
|
+
* If the element can have inner content, whatever happens inside a yielded block becomes that inner content.
|
89
|
+
|
90
|
+
To use this component, we can call `render` in either the `view_template` of another component or the `page_template` of a [page](/pages):
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
def view_template
|
94
|
+
form do
|
95
|
+
render Button.new(label: "Submit")
|
96
|
+
render Button.new(label: "Nevermind", size: :small, color: :red)
|
97
|
+
render Button.new(color: :green) do
|
98
|
+
img(src: "/images/ok.png", alt: "OK icon")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Note that the block passed to `render` is the block available when `yield` is called inside `view_template`.
|
105
|
+
|
106
|
+
There are two special types of components beyond what we have just seen. *Global* Components and *Page private* components.
|
107
|
+
|
108
|
+
### Global Components
|
109
|
+
|
110
|
+
As we saw above, creating a component is just like creating any Ruby class: you call `.new` on it. If you create a component that uses request-level data, such as the flash or session, it would mean that any page or component that used *that* component would need to accept the flash or session as a parameter to its initializer, even if it it was otherwise not needed.
|
111
|
+
|
112
|
+
In those cases, `global_component` can be used to leverage [keyword injection](/keyword-injection) to have Brut create the component. That way, its initializer's parameters don't need to be passed into the page or component using the global component.
|
113
|
+
|
114
|
+
Suppose we had a component to display the flash:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class FlashMessage < AppComponent
|
118
|
+
|
119
|
+
def initialize(flash:)
|
120
|
+
@flash = flash
|
121
|
+
end
|
122
|
+
|
123
|
+
def view_template
|
124
|
+
if @flash.notice?
|
125
|
+
div(role: "status") { @flash.notice }
|
126
|
+
elsif @flash.alert?
|
127
|
+
div(role: "alert") { @flash.alert }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
To use it without having to instantiate it, call `global_component` with the component's class:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class HomePage < AppPage
|
137
|
+
def page_template
|
138
|
+
header do
|
139
|
+
render global_component(FlashMessage)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
> [!IMPORTANT]
|
146
|
+
> Brut currently requires an all-or-nothing approach to global components. Either
|
147
|
+
> the component can be injected with all its initializer parameters or it must
|
148
|
+
> be created explicitly by the page or component. You cannot have a component
|
149
|
+
> receive request-level keyword injection for some parameters with the page
|
150
|
+
> providing the rest.
|
151
|
+
|
152
|
+
Components can also be scoped to a page.
|
153
|
+
|
154
|
+
### Page Private Components
|
155
|
+
|
156
|
+
Often, components are helpful to simplifying a page's template or managing re-use within a page, but such a component isn't designed for use outside that page. For example, if the page renders a table, but the logic for each row is complex, you may want that in a separate component, even though it would be useless outside the page.
|
157
|
+
|
158
|
+
Brut provides a way to create a *page private* component that exists as an inner class of a page. It's not truly private, since it's still a Ruby class anyone can use, but it's form and source location communicate intent.
|
159
|
+
|
160
|
+
Suppose our `HomePage` has a list of widgets on it, but we want each widget's HTML managed by a separate component:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
class HomePage < AppPage
|
164
|
+
def page_template
|
165
|
+
header do
|
166
|
+
h1 { "Check out these Widgets!" }
|
167
|
+
end
|
168
|
+
main do
|
169
|
+
ul do
|
170
|
+
DB::Widget.all.each do |widget|
|
171
|
+
render(HomePage::WidgetListItem.new(widget:))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
`HomePage::WidgetListItem` can be created like so:
|
180
|
+
|
181
|
+
```
|
182
|
+
bin/scaffold component --page=HomePage WidgetListItem
|
183
|
+
```
|
184
|
+
|
185
|
+
This will create `app/src/front_end/pages/home_page/widget_list_item.rb`, which you can then implement like a normal component:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# app/src/front_end/pages/home_page/widget_list_item.rb
|
189
|
+
class HomePage::WidgetListItem < AppComponent
|
190
|
+
def initialize(widget:)
|
191
|
+
@widget = widget
|
192
|
+
end
|
193
|
+
|
194
|
+
def view_template
|
195
|
+
li do
|
196
|
+
h2 { @widget.name }
|
197
|
+
p { @widget.description }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
The only special thing about a page private component is that it can access the page's I18n translations. As we'll discussion in [I18n](/i18n), The `t` method will try to locate translations based on the page on which `t` is called. A page private component will also trigger this behavior, but a normal component will not.
|
204
|
+
|
205
|
+
For example, the following code will look for `pages.HomePage.status.«status»` when generated the `<h3>`:
|
206
|
+
|
207
|
+
```ruby {10}
|
208
|
+
# app/src/front_end/pages/home_page/widget_list_item.rb
|
209
|
+
class HomePage::WidgetListItem < AppComponent
|
210
|
+
def initialize(widget:)
|
211
|
+
@widget = widget
|
212
|
+
end
|
213
|
+
|
214
|
+
def view_template
|
215
|
+
li do
|
216
|
+
h2 { @widget.name }
|
217
|
+
h3 { t([ :status, @widget.status ]) }
|
218
|
+
p { @widget.description }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
```
|
223
|
+
|
224
|
+
## Testing
|
225
|
+
|
226
|
+
Test widgets exactly as you would [pages](/pages#testing). The only difference is that components always render HTML and have no `before_generate` concept.
|
227
|
+
|
228
|
+
## Recommended Practices
|
229
|
+
|
230
|
+
The [recommended practices for pages](/pages#recommended-practices) all apply to components, too.
|
231
|
+
|
232
|
+
Beyond that, components are intended to be lightweight, so use them liberally. Any page or component that has complex markup can be extracted to another component and more easily unit-tested.
|
233
|
+
|
234
|
+
|
235
|
+
## Technical Notes
|
236
|
+
|
237
|
+
> [!IMPORTANT]
|
238
|
+
> Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
|
239
|
+
> internals, the source code is always more correct.
|
240
|
+
|
241
|
+
_Last Updated May 5, 2025_
|
242
|
+
|
243
|
+
As mentioned, components are Phlex components, but have various helpers mixed-into them. Components are currently tightly coupled to Phlex and there is no plan to allow alternate implementations of view logic that isn't supported by Phlex.
|