seams 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +212 -12
- data/README.md +235 -82
- data/lib/generators/seams/accounts/accounts_generator.rb +31 -3
- data/lib/generators/seams/accounts/templates/app/controllers/memberships_controller.rb.tt +100 -0
- data/lib/generators/seams/accounts/templates/app/views/memberships/index.html.erb.tt +52 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +10 -4
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +35 -2
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +13 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/authorization_spec.rb.tt +102 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/memberships_flow_spec.rb.tt +145 -0
- data/lib/generators/seams/admin/admin_generator.rb +51 -4
- data/lib/generators/seams/admin/templates/README.md.tt +33 -3
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +156 -8
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +9 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +9 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +7 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +12 -2
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +7 -1
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/belongs_to.rb.tt +12 -0
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/dashboard_option.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/has_many.rb.tt +12 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +4 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +51 -10
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +4 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +7 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_index_header.html.erb.tt +57 -0
- data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_navigation.html.erb.tt +14 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +24 -14
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +3 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +3 -1
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +4 -2
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +1 -2
- data/lib/generators/seams/auth/auth_generator.rb +1 -2
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +3 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +4 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +5 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +5 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +5 -1
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +12 -1
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/billing/billing_generator.rb +4 -3
- data/lib/generators/seams/billing/templates/README.md.tt +26 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +2 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +2 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +8 -7
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +9 -7
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +18 -3
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +7 -21
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +10 -10
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +40 -14
- data/lib/generators/seams/billing/templates/lib/stripe/payload.rb.tt +90 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +11 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +15 -4
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +7 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +18 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +26 -5
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +17 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +47 -1
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +39 -1
- data/lib/generators/seams/design/design_generator.rb +647 -0
- data/lib/generators/seams/design/templates/README.md.tt +129 -0
- data/lib/generators/seams/design/templates/app/assets/tailwind/_tokens.css +322 -0
- data/lib/generators/seams/design/templates/app/assets/tailwind/themes/_quire.css +42 -0
- data/lib/generators/seams/design/templates/app/controllers/design/dashboard_controller.rb.tt +30 -0
- data/lib/generators/seams/design/templates/app/controllers/design/guide_controller.rb.tt +28 -0
- data/lib/generators/seams/design/templates/app/form_builders/design/form_builder.rb.tt +90 -0
- data/lib/generators/seams/design/templates/app/helpers/design/ui_helper.rb.tt +37 -0
- data/lib/generators/seams/design/templates/app/views/design/dashboard/index.html.erb.tt +38 -0
- data/lib/generators/seams/design/templates/app/views/design/guide/index.html.erb.tt +19 -0
- data/lib/generators/seams/design/templates/app/views/layouts/application.html.erb.tt +61 -0
- data/lib/generators/seams/design/templates/app/views/layouts/design/guide.html.erb.tt +21 -0
- data/lib/generators/seams/design/templates/app/views/ui/_banner.html.erb.tt +10 -0
- data/lib/generators/seams/design/templates/app/views/ui/_breadcrumb.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/_build_row.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_button.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/_card.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_chapter_row.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/app/views/ui/_checkbox.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_counter.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_data_table.html.erb.tt +25 -0
- data/lib/generators/seams/design/templates/app/views/ui/_dialog.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_diff.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_drawer.html.erb.tt +7 -0
- data/lib/generators/seams/design/templates/app/views/ui/_empty.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_field.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_icon.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_icon_sprite.html.erb.tt +22 -0
- data/lib/generators/seams/design/templates/app/views/ui/_input_group.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/app/views/ui/_kbd.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/_menu.html.erb.tt +18 -0
- data/lib/generators/seams/design/templates/app/views/ui/_meter.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/_note.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_outline.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/_pagination.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_panel.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_popover.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_radio.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_savestate.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_segmented.html.erb.tt +7 -0
- data/lib/generators/seams/design/templates/app/views/ui/_stepper.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_switch.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/_tag.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_toast.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_toolbar.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_banner.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_breadcrumb.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_build_row.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_button.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_card.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_chapter_row.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_checkbox.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_counter.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_data_table.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_dialog.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_diff.html.erb.tt +10 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_drawer.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_empty.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_field.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_input_group.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_kbd.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_menu.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_meter.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_note.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_outline.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_pagination.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_panel.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_popover.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_radio.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_savestate.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_segmented.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_stepper.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_switch.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_tag.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_toast.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_toolbar.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/lib/design/components.rb.tt +22 -0
- data/lib/generators/seams/design/templates/lib/design.rb.tt +18 -0
- data/lib/generators/seams/design/templates/lib/engine.rb.tt +38 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/component_generator.rb.tt +52 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/templates/component.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/templates/preview.html.erb.tt +3 -0
- data/lib/generators/seams/design/templates/spec/runtime/design_boot_spec.rb.tt +79 -0
- data/lib/generators/seams/design/templates/spec/runtime/form_builder_spec.rb.tt +97 -0
- data/lib/generators/seams/design/templates/spec/runtime/guide_spec.rb.tt +40 -0
- data/lib/generators/seams/design/templates/spec/runtime/ui_components_spec.rb.tt +70 -0
- data/lib/generators/seams/engine/engine_generator.rb +6 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +1 -1
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +9 -0
- data/lib/generators/seams/install/install_generator.rb +72 -6
- data/lib/generators/seams/install/templates/Dockerfile.tt +2 -2
- data/lib/generators/seams/install/templates/bin_seams.tt +7 -4
- data/lib/generators/seams/install/templates/ci.yml.tt +18 -8
- data/lib/generators/seams/install/templates/deploy.yml.tt +4 -2
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +1 -1
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +4 -2
- data/lib/generators/seams/install/templates/herb.yml.tt +10 -0
- data/lib/generators/seams/install/templates/lefthook.yml.tt +25 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -1
- data/lib/generators/seams/install/templates/seams.rake.tt +13 -1
- data/lib/generators/seams/install/templates/strong_migrations.rb.tt +26 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +3 -2
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +5 -13
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +1 -0
- data/lib/generators/seams/notifications/templates/lib/preferences.rb.tt +36 -0
- data/lib/generators/seams/permissions/permissions_generator.rb +97 -0
- data/lib/generators/seams/permissions/templates/config/initializers/seams_permissions.rb.tt +35 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +27 -11
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +5 -13
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +2 -4
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +13 -0
- data/lib/seams/cli/list.rb +15 -0
- data/lib/seams/cli/quality.rb +46 -5
- data/lib/seams/cli/resolve.rb +9 -5
- data/lib/seams/cli/test_changed.rb +5 -1
- data/lib/seams/cli.rb +20 -0
- data/lib/seams/configuration.rb +24 -1
- data/lib/seams/cops/no_cross_engine_dependency.rb +39 -7
- data/lib/seams/cops/no_cross_engine_model_access.rb +79 -1
- data/lib/seams/event_registry.rb +19 -0
- data/lib/seams/events/adapter.rb +9 -0
- data/lib/seams/events/adapters/active_support.rb +4 -0
- data/lib/seams/events/publisher.rb +29 -0
- data/lib/seams/events.rb +1 -0
- data/lib/seams/generators/dummy_app_writer.rb +21 -0
- data/lib/seams/generators/follow_up_generator.rb +1 -2
- data/lib/seams/generators/host_injector.rb +9 -2
- data/lib/seams/generators/splicer.rb +2 -2
- data/lib/seams/observability/adapter.rb +22 -0
- data/lib/seams/observability.rb +6 -0
- data/lib/seams/permission_registry.rb +66 -0
- data/lib/seams/permissions.rb +120 -0
- data/lib/seams/version.rb +1 -1
- data/lib/seams.rb +7 -0
- metadata +107 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '080facbda9aa03ebeec4885f3745dd0c100d698465dcfcd7bbf35c62dd634eff'
|
|
4
|
+
data.tar.gz: d9a6b438e0ff12c37ba4006e680a7d01f30358c1bb351cdf2e47fee9110f13be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e607f46a0b749397ad5b7f7a8a1927dcd267ca14dbf77107c938269bd17c17d426ac38b98f88ba734c7d7a27adceb150973fa331e2d1b302515102b96afdccd
|
|
7
|
+
data.tar.gz: b708e636f64a8502215b811102640df371ed57846ecd54a44dbd8ea0391f84848f4f42350b3111ddab113eb96a19a75d529f19c45b8a0726b7e6e0cd4c62b9cd
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,206 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] — 2026-09-27
|
|
11
|
+
|
|
12
|
+
> [!WARNING]
|
|
13
|
+
> Breaking changes for hosts (pre-1.0, so this is a minor bump):
|
|
14
|
+
>
|
|
15
|
+
> - Generated billing code targets `stripe ~> 19.0` (was `~> 13.0`) and
|
|
16
|
+
> raises `Billing::GatewayError` where Stripe errors used to escape.
|
|
17
|
+
> Hosts that rescued `Stripe::*` from the generated services must rescue
|
|
18
|
+
> `Billing::GatewayError`. Match your Stripe webhook endpoint's API
|
|
19
|
+
> version to `2026-08-26.dahlia`.
|
|
20
|
+
> - Generated code is not rewritten by a gem upgrade. See "Fixed" for the
|
|
21
|
+
> manual steps (engine `spec/dummy/config/cable.yml`, admin engine).
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Integration suite: a default `seams:install` host must resolve its
|
|
26
|
+
bundle on a multi-platform lockfile (x86_64-linux, aarch64-linux,
|
|
27
|
+
arm64-darwin, x86_64-darwin). Guards the #42 class, where a gem shipping
|
|
28
|
+
only some native platforms broke `bundle` on Rails 8 lockfiles. Completes
|
|
29
|
+
#48.
|
|
30
|
+
- Community and AI-era repository standards: `SUPPORT.md`, `GOVERNANCE.md`,
|
|
31
|
+
`MAINTAINERS.md`, `CITATION.cff`, `.editorconfig`, `.github/FUNDING.yml`,
|
|
32
|
+
an `llms.txt` machine-readable index, and `AGENTS.md` instructions for AI
|
|
33
|
+
coding agents. `doc/explanation/ARCHITECTURE.md` gains a "Where this design would
|
|
34
|
+
strain" section. Blank issues are now routed through the issue chooser.
|
|
35
|
+
|
|
36
|
+
- Install generator: ships an opinionated quality toolchain — a host is hardened
|
|
37
|
+
from the first CLI run. `seams:install` installs **strong_migrations**
|
|
38
|
+
(+ initializer, audits all migrations via `start_after = 0`) and **lefthook**
|
|
39
|
+
(`lefthook.yml`: pre-commit rubocop, pre-push specs) by default, and wires a
|
|
40
|
+
from-scratch `db:migrate` safety check into the generated CI. The rubocop/
|
|
41
|
+
brakeman/bundle-audit gems the generated CI shells out to are now injected
|
|
42
|
+
into the host Gemfile too. **herb** (HTML+ERB lint + `.herb.yml`) is available
|
|
43
|
+
but **off by default** — opt in with `--herb` — because its platform-specific
|
|
44
|
+
native gem breaks cold installs on multi-platform lockfiles (see commit
|
|
45
|
+
6c16c52). strong_migrations and lefthook are opt-out via
|
|
46
|
+
`--no-strong-migrations` / `--no-lefthook`. Also closes two gaps where Seams
|
|
47
|
+
generated migrations that nothing validated — the host CI now vets them.
|
|
48
|
+
- Permissions engine (`bin/seams permissions`): generates a host-editable
|
|
49
|
+
role → ability grant map at `config/initializers/seams_permissions.rb`, backed
|
|
50
|
+
by `Seams::PermissionRegistry` / `Seams::Permissions` and the
|
|
51
|
+
`authorize_permission!` controller helper. See [doc/reference/PERMISSIONS.md].
|
|
52
|
+
- Admin engine (`bin/seams admin`): opt-in Administrate dashboards with a Pundit
|
|
53
|
+
`Platform`/`Tenant` policy split and an admin audit trail writing
|
|
54
|
+
`Core::AuditLog` rows. See [doc/explanation/ARCHITECTURE_WAVE_11.md].
|
|
55
|
+
- Design engine (`bin/seams design`, `--shell`): a themeable design system —
|
|
56
|
+
33 `ui_*` components, Tailwind v4 `@theme` tokens + component CSS,
|
|
57
|
+
`Design::FormBuilder`, a `/design/guide` gallery, and a `design:component`
|
|
58
|
+
generator; `--shell` also generates an application layout and a starter
|
|
59
|
+
signed-in dashboard. See [doc/design-system/DESIGN_SYSTEM.md].
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Generated host pins: rspec-rails is injected as `">= 7.1", "< 9"` (was
|
|
64
|
+
`"~> 7.1"`) in the host Gemfile and each engine Gemfile, so Rails 7.2+
|
|
65
|
+
hosts get rspec-rails 8.x. The generated CI uses `actions/checkout@v7`
|
|
66
|
+
and a `postgres:18` service; the Kamal Postgres accessory comment moves
|
|
67
|
+
to `postgres:18` and its volume to `/var/lib/postgresql` (18's layout).
|
|
68
|
+
- Billing generator: the generated engine now targets stripe-ruby 19
|
|
69
|
+
(`gem "stripe", "~> 19.0"`, was `~> 13.0`), which pins Stripe API version
|
|
70
|
+
`2026-08-26.dahlia`. Existing hosts run `bundle update stripe` and should
|
|
71
|
+
move their webhook endpoint to the same API version (the handlers accept
|
|
72
|
+
both shapes meanwhile). Changes in the generated code:
|
|
73
|
+
- New `Billing::Stripe::Payload` reads Stripe objects and webhook hashes
|
|
74
|
+
alike. Handlers and `Invoices::SyncService` read the invoice's
|
|
75
|
+
subscription from `invoice.parent.subscription_details.subscription` and
|
|
76
|
+
the period end from `items.data[0].current_period_end` (both moved in
|
|
77
|
+
2025-03-31.basil). The old top-level `invoice.subscription` and
|
|
78
|
+
`subscription.current_period_end` are still read as fallbacks.
|
|
79
|
+
- `invoice.paid` now stores Stripe's `status_transitions.paid_at` as
|
|
80
|
+
`paid_at` instead of the time the webhook arrived.
|
|
81
|
+
- Fixed `#dig` calls on SDK responses (`Gateways::Stripe#normalise_subscription`,
|
|
82
|
+
`Invoices::SyncService#paid_at_for`). `Stripe::StripeObject` has no `#dig`,
|
|
83
|
+
so these raised `NoMethodError` on real API responses.
|
|
84
|
+
- `Billing::Stripe::Client` re-raises every `Stripe::StripeError` as
|
|
85
|
+
`Billing::GatewayError` (original on `#cause`). Every generated caller
|
|
86
|
+
already rescued `Billing::GatewayError`, but the gem-backed client never
|
|
87
|
+
raised it, so Stripe errors escaped `StripeService`, the checkout and
|
|
88
|
+
portal services, and `CreateLifetimeSessionService`. Hosts that rescued
|
|
89
|
+
`Stripe::*` errors from these services should rescue
|
|
90
|
+
`Billing::GatewayError` instead.
|
|
91
|
+
- The webhook endpoint answers `400` (not `500`) to a signed v2 thin event;
|
|
92
|
+
stripe-ruby 19's `Stripe::Webhook.construct_event` raises `ArgumentError`
|
|
93
|
+
for those.
|
|
94
|
+
- Stripe event fixtures under `spec/fixtures/stripe/` use the dahlia shapes
|
|
95
|
+
and carry `api_version`.
|
|
96
|
+
- Dependencies: Ruby 4.0.7 (repo, CI, and the generated host's
|
|
97
|
+
`.ruby-version` / Dockerfile default), Rails 8.1.4, RuboCop 1.91 (+
|
|
98
|
+
rubocop-rails 2.38, rubocop-performance 1.27), SimpleCov 1.3, brakeman
|
|
99
|
+
8.0.6, sqlite3 2.9.6, yard 0.9.45, and the docs-site on Astro 7.3 / Starlight 0.42. Patches the
|
|
100
|
+
websocket-driver 0.8.0 advisories (CVE-2026-54463/54464/54465/61666)
|
|
101
|
+
and the docs-site devalue, nanoid, and postcss advisories. CI moves to
|
|
102
|
+
`actions/setup-node@v7`.
|
|
103
|
+
- Documentation: `doc/` is now organised into Diátaxis folders —
|
|
104
|
+
`tutorials/`, `how-to/`, `reference/`, `design-system/`,
|
|
105
|
+
`explanation/`, plus `internal/` for maintainer-facing working
|
|
106
|
+
documents. Published URLs on the docs site are unchanged (the site
|
|
107
|
+
flattens the folders), and the site's in-page cross-links — which
|
|
108
|
+
previously shipped as raw `.md` hrefs and 404'd — now resolve to the
|
|
109
|
+
real page URLs.
|
|
110
|
+
- Engine generator: ApplicationController now requires authentication by default. Opt out via `skip_before_action :authenticate_identity!` in controllers serving public flows. [BREAKING for hosts that explicitly relied on engines being unauthenticated by default.]
|
|
111
|
+
- Notifications generator: preferences controller now uses an explicit
|
|
112
|
+
permit list from the channel/type registry rather than `permit!`.
|
|
113
|
+
Eliminates the brakeman mass-assignment warning shipped with generated
|
|
114
|
+
engines. Hosts that have already ejected
|
|
115
|
+
`app/controllers/notifications/preferences_controller.rb` will not
|
|
116
|
+
re-generate it (per the `template_unless_ejected` contract); to pick
|
|
117
|
+
up the new behaviour, replace
|
|
118
|
+
`params.require(:preferences).permit!` in the ejected copy with
|
|
119
|
+
`params.require(:preferences).permit(*Notifications::Preferences.allowed_keys)`
|
|
120
|
+
and require `notifications/preferences` from
|
|
121
|
+
`lib/notifications.rb`.
|
|
122
|
+
- Teams generator: `TeamsController` now includes `Teams::Authorization` and applies `require_team_member!` as a `before_action` on `show`, and `require_team_admin!` on `edit`, `update`, and `destroy`. Previously those actions had no resource-level membership guard, letting any authenticated identity act on any team by ID. The `require_team_member!` and `require_team_admin!` predicates in the concern now redirect to `teams_path` with an alert rather than returning a bare 403, matching the convention used by the other team controllers.
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- Admin engine UI: the sidebar and every New / show / edit / destroy link
|
|
127
|
+
were missing, because Administrate reads the host's routes and a mounted
|
|
128
|
+
engine's routes are not in them. `existing_action?` and a navigation
|
|
129
|
+
partial now read the engine's routes; `authorized_action?` uses the
|
|
130
|
+
policy of the resource being linked. An `_index_header` override builds
|
|
131
|
+
the New link from the controller. Creates now write an admin audit row
|
|
132
|
+
(Administrate's `create` never set `requested_resource`). The admin
|
|
133
|
+
README documents the authenticator and membership resolver that tenant
|
|
134
|
+
mode needs.
|
|
135
|
+
- `bin/audit` (the pre-push gate) failed from a linked git worktree:
|
|
136
|
+
the hook's exported `GIT_DIR` made `bundle-audit --update` pull its
|
|
137
|
+
advisory database against this repo. It now clears git's hook
|
|
138
|
+
variables for that command.
|
|
139
|
+
- gemspec: `required_ruby_version` is now `>= 3.3.0` (was `>= 4.0.0`), so
|
|
140
|
+
`bundle add seams` resolves on Ruby 3.3 and 3.4 hosts. CI runs the spec
|
|
141
|
+
suite on 3.3, 3.4, and 4.0. RuboCop targets 3.3 so 4.0-only syntax is
|
|
142
|
+
caught. Ruby 3.2 is end-of-life and SimpleCov 1.x requires 3.3.
|
|
143
|
+
Fixes #79.
|
|
144
|
+
- `host_inject_gem` dropped every version requirement after the first, so
|
|
145
|
+
a range like `(">= 7.1", "< 9")` was written as `">= 7.1"` only.
|
|
146
|
+
- Admin generator: the host no longer gets `mount Admin::Engine` and an
|
|
147
|
+
`Admin.configure` initializer stub from the base engine step. The engine
|
|
148
|
+
lives at `Seams::Admin::Engine`, so every host that ran `seams:admin`
|
|
149
|
+
raised `NameError` on boot. `seams:engine` gains `--skip-host-wiring`
|
|
150
|
+
for canonical generators that wire the host themselves. The admin engine
|
|
151
|
+
now runs in the rails-new integration suite.
|
|
152
|
+
- Admin engine: every dashboard request failed. Fixed the chain found by
|
|
153
|
+
driving a generated host over HTTP:
|
|
154
|
+
- routes point at `/admin/...` controllers (isolate_namespace resolved
|
|
155
|
+
`admin/identities` to `Seams::Admin::Admin::IdentitiesController`) and
|
|
156
|
+
sit in `scope as: :admin` so Administrate's `admin_*` helpers exist;
|
|
157
|
+
- the base controller includes `Auth::Authentication`, so
|
|
158
|
+
`current_identity` resolves; signed-out visitors are redirected to
|
|
159
|
+
sign-in, non-staff get 403;
|
|
160
|
+
- policies resolve as `Admin::<Platform|Tenant>::<Resource>Policy` via
|
|
161
|
+
Pundit's `policy_class:` (the array lookup produced
|
|
162
|
+
`Module::Auth::IdentityPolicy`);
|
|
163
|
+
- dashboards declare `self.model`; association fields take a
|
|
164
|
+
`dashboard:` option (`Admin::Fields::BelongsTo` / `HasMany`);
|
|
165
|
+
- the `Admin::*` controllers get the engine's route helpers, the
|
|
166
|
+
`accounts`/`teams` membership route-key clash is dispatched by record
|
|
167
|
+
class, flash messages use the real model name, and `verify_authorized`
|
|
168
|
+
no longer names a non-action (ActionNotFound on Rails 7.1+);
|
|
169
|
+
- the admin dummy app registers the abilities its tenant policies
|
|
170
|
+
check, and the tenant role specs use a concrete policy.
|
|
171
|
+
The integration suite signs in as anonymous, non-staff, and staff and
|
|
172
|
+
asserts every dashboard's index and new page plus a create.
|
|
173
|
+
- Generated `bin/docker-entrypoint` now runs `db:prepare` for the
|
|
174
|
+
Dockerfile's own `bundle exec rails server -b 0.0.0.0` command. It only
|
|
175
|
+
checked the first three arguments, where `server` never appears, so a
|
|
176
|
+
seams-built container started against an empty database. Verified by
|
|
177
|
+
building and running a generated host (all engines) against Postgres 18.
|
|
178
|
+
- Engine dummy apps now ship `spec/dummy/config/cable.yml` (test adapter).
|
|
179
|
+
solid_cable 4.1 reads `config_for("cable")` at boot, so every generated
|
|
180
|
+
engine spec suite failed to load in a host that bundles solid_cable.
|
|
181
|
+
Engines generated before this release need the file added by hand:
|
|
182
|
+
`engines/*/spec/dummy/config/cable.yml` containing `test:` /
|
|
183
|
+
` adapter: test`.
|
|
184
|
+
- Nightly link check: lychee retries each link 5 times (10s apart) before
|
|
185
|
+
reporting it, and a failing run updates the open `broken-link` issue
|
|
186
|
+
instead of filing a new one. Closes #93 and #109, both transient
|
|
187
|
+
connection resets on contributor-covenant.org.
|
|
188
|
+
- Teams generator: `InvitationsController#accept` now redirects unauthenticated requests to the sign-in path (stashing the token in the session under `pending_invitation_token`) instead of raising `ActiveRecord::RecordInvalid`. The host's sign-in flow is responsible for reading `return_to` and redirecting back after authentication.
|
|
189
|
+
- Teams generator: `InvitationsController#accept` now verifies that the signed-in identity's email matches the invitation email (case-insensitively) before creating the membership. Previously any authenticated identity holding a valid token could accept the invitation under a different account.
|
|
190
|
+
- Billing generator: `scoped_invoices` / `scoped_subscriptions` now return
|
|
191
|
+
`.none` when the current customer ref is nil. The previous behaviour was
|
|
192
|
+
an **information disclosure**: Rails translates
|
|
193
|
+
`.where(customer_ref: nil)` to `WHERE customer_ref IS NULL`, which
|
|
194
|
+
matches every invoice and subscription row whose `customer_ref` has not
|
|
195
|
+
yet been linked to a Stripe customer — typically including manual,
|
|
196
|
+
draft, or test records seeded by other staff. The fix returns an empty
|
|
197
|
+
relation when there is no customer to scope by.
|
|
198
|
+
|
|
199
|
+
Hosts that have already ejected
|
|
200
|
+
`app/controllers/billing/invoices_controller.rb` or
|
|
201
|
+
`subscriptions_controller.rb` will not re-generate them (per the
|
|
202
|
+
`template_unless_ejected` contract). To pick up the fix, prepend each
|
|
203
|
+
scope helper with:
|
|
204
|
+
|
|
205
|
+
return Billing::Invoice.none if current_billing_customer_ref.nil?
|
|
206
|
+
|
|
207
|
+
(likewise `Billing::Subscription.none` in the subscriptions helper)
|
|
208
|
+
before the existing `.where(customer_ref: …)` line.
|
|
209
|
+
|
|
10
210
|
## [0.1.0] — 2026-05-10
|
|
11
211
|
|
|
12
212
|
First public release on rubygems.org. The cumulative changelog from
|
|
@@ -21,9 +221,9 @@ notifications, billing, teams) can mount an Administrate-backed admin
|
|
|
21
221
|
surface at `/admin` covering all twelve canonical seams models with a
|
|
22
222
|
single command. Dashboards, two-mode authorization, audit-log
|
|
23
223
|
auto-write, and the four config knobs all ship out of the box. See
|
|
24
|
-
[`doc/ARCHITECTURE_WAVE_11.md`](doc/ARCHITECTURE_WAVE_11.md) for the
|
|
224
|
+
[`doc/explanation/ARCHITECTURE_WAVE_11.md`](doc/explanation/ARCHITECTURE_WAVE_11.md) for the
|
|
25
225
|
new architecture material; the framework selection rationale lives in
|
|
26
|
-
|
|
226
|
+
`proposals/admin_engine_administrate.md`.
|
|
27
227
|
|
|
28
228
|
#### Added
|
|
29
229
|
|
|
@@ -97,8 +297,8 @@ follow-up generators target those points to add features without
|
|
|
97
297
|
re-templating the whole engine; and `bin/seams resolve --eject`
|
|
98
298
|
marks any single host file as host-owned so subsequent
|
|
99
299
|
`bin/seams <engine>` runs leave it alone. See
|
|
100
|
-
[`doc/ARCHITECTURE_WAVE_10.md`](doc/ARCHITECTURE_WAVE_10.md) for the
|
|
101
|
-
addendum and [`doc/WRITING_FOLLOW_UP_GENERATORS.md`](doc/WRITING_FOLLOW_UP_GENERATORS.md)
|
|
300
|
+
[`doc/explanation/ARCHITECTURE_WAVE_10.md`](doc/explanation/ARCHITECTURE_WAVE_10.md) for the
|
|
301
|
+
addendum and [`doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md`](doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md)
|
|
102
302
|
for the author's guide.
|
|
103
303
|
|
|
104
304
|
#### Added
|
|
@@ -116,8 +316,8 @@ for the author's guide.
|
|
|
116
316
|
core Configuration class). Marker shape:
|
|
117
317
|
`# seams:insertion-point <engine>.<area>.<scope>` — ASCII only,
|
|
118
318
|
greppable, parses through every Ruby linter. Format spec in
|
|
119
|
-
[`doc/INSERTION_POINTS.md`](doc/INSERTION_POINTS.md); canonical list
|
|
120
|
-
in [`doc/INSERTION_POINTS_CATALOGUE.md`](doc/INSERTION_POINTS_CATALOGUE.md).
|
|
319
|
+
[`doc/reference/INSERTION_POINTS.md`](doc/reference/INSERTION_POINTS.md); canonical list
|
|
320
|
+
in [`doc/reference/INSERTION_POINTS_CATALOGUE.md`](doc/reference/INSERTION_POINTS_CATALOGUE.md).
|
|
121
321
|
- **`bin/seams resolve` CLI.** Three modes:
|
|
122
322
|
- `--eject <engine>/<file>` — prepends a
|
|
123
323
|
`# seams:ejected from <engine>.<path>` header to the host file;
|
|
@@ -144,12 +344,12 @@ for the author's guide.
|
|
|
144
344
|
`auth.configuration.oauth_providers` marker, and writes a matching
|
|
145
345
|
spec. Idempotent on rerun.
|
|
146
346
|
- **Documentation.**
|
|
147
|
-
[`doc/INSERTION_POINTS.md`](doc/INSERTION_POINTS.md) (format spec),
|
|
148
|
-
[`doc/INSERTION_POINTS_CATALOGUE.md`](doc/INSERTION_POINTS_CATALOGUE.md)
|
|
347
|
+
[`doc/reference/INSERTION_POINTS.md`](doc/reference/INSERTION_POINTS.md) (format spec),
|
|
348
|
+
[`doc/reference/INSERTION_POINTS_CATALOGUE.md`](doc/reference/INSERTION_POINTS_CATALOGUE.md)
|
|
149
349
|
(canonical 33-marker list),
|
|
150
|
-
[`doc/WRITING_FOLLOW_UP_GENERATORS.md`](doc/WRITING_FOLLOW_UP_GENERATORS.md)
|
|
350
|
+
[`doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md`](doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md)
|
|
151
351
|
(author's guide), and
|
|
152
|
-
[`doc/ARCHITECTURE_WAVE_10.md`](doc/ARCHITECTURE_WAVE_10.md)
|
|
352
|
+
[`doc/explanation/ARCHITECTURE_WAVE_10.md`](doc/explanation/ARCHITECTURE_WAVE_10.md)
|
|
153
353
|
(architecture addendum with splice + eject sequence diagrams).
|
|
154
354
|
- **Host-facing surfaces updated.**
|
|
155
355
|
`bin/seams help` and `bin/seams resolve --help` document the new
|
|
@@ -161,7 +361,7 @@ for the author's guide.
|
|
|
161
361
|
|
|
162
362
|
Replaces the conflated `Auth::User` (which owned credentials AND
|
|
163
363
|
tenant-membership concepts) with three peer engines, each with one
|
|
164
|
-
clear responsibility. See `doc/UPGRADING_FROM_WAVE_8.md` for the
|
|
364
|
+
clear responsibility. See `doc/how-to/UPGRADING_FROM_WAVE_8.md` for the
|
|
165
365
|
migration story.
|
|
166
366
|
|
|
167
367
|
#### Added
|
|
@@ -245,7 +445,7 @@ migration story.
|
|
|
245
445
|
state holders are intentionally readable from any engine. The
|
|
246
446
|
cop now treats `Current` as a framework-level constant and
|
|
247
447
|
exempts it from the boundary rule. Documented in
|
|
248
|
-
`doc/CURRENT_ATTRIBUTES.md` and inline in the cop's docstring.
|
|
448
|
+
`doc/reference/CURRENT_ATTRIBUTES.md` and inline in the cop's docstring.
|
|
249
449
|
- `Teams::AccountScoped` now references `Teams::Current.team`
|
|
250
450
|
(previously a bare `Current.team` that resolved to nothing
|
|
251
451
|
inside `module Teams` — silently making the default_scope a
|
data/README.md
CHANGED
|
@@ -1,104 +1,257 @@
|
|
|
1
1
|
# Seams
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/seams)
|
|
4
|
+
[](https://github.com/Davidslv/seams/actions/workflows/ci.yml)
|
|
5
|
+
[](https://davidslv.github.io/seams/)
|
|
6
|
+
[](https://rubydoc.info/gems/seams)
|
|
7
|
+
[](LICENSE)
|
|
4
8
|
|
|
5
|
-
Seams
|
|
6
|
-
boundaries, independent testing, team autonomy — without the
|
|
7
|
-
operational cost. You ship a single Rails app. You think in
|
|
8
|
-
independent engines.
|
|
9
|
+
Seams generates modular Rails engines inside your Rails app.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
> Rails: Architecture for the Long Game](https://davidslv.uk/modular-rails/)**.
|
|
12
|
-
> The book is the artefact; this gem is the executable shorthand. See
|
|
13
|
-
> [seams-example](https://github.com/Davidslv/seams-example) for a
|
|
14
|
-
> reference host that wires every canonical engine end-to-end.
|
|
11
|
+
You ship one Rails app. Inside it, each feature (auth, accounts, billing, teams, and so on) lives in its own engine under `engines/`. Each engine has its own models, tests, and boundaries. Engines talk to each other through events, not by reaching into each other's code. Custom RuboCop cops enforce that.
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
Every generated file is plain Rails code in your repo. You can read it, change it, or delete it. Nothing is hidden behind the gem.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
> [!NOTE]
|
|
16
|
+
> Seams is the executable companion to the book **[Modular Rails: Architecture for the Long Game](https://davidslv.uk/modular-rails/)**. The full guides live on the **[documentation site](https://davidslv.github.io/seams/)**. [seams-example](https://github.com/Davidslv/seams-example) is a reference host with every engine wired up.
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
| | Supported | Tested in CI |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Ruby | 3.3 or newer | 3.3, 3.4, 4.0.7 |
|
|
23
|
+
| Rails | 7.1 or newer (below 9) | 8.1.4 |
|
|
24
|
+
| Database | PostgreSQL | PostgreSQL 18 |
|
|
25
|
+
|
|
26
|
+
> [!IMPORTANT]
|
|
27
|
+
> **PostgreSQL is required.** The generated engines use `jsonb` columns, and each engine's test suite runs against Postgres. SQLite and MySQL are not supported.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
> [!WARNING]
|
|
32
|
+
> **Install from GitHub for now.** The only release on RubyGems is `0.1.0` (May 2026). It requires Ruby 4.0 or newer and predates many fixes, including the admin engine working at all and Ruby 3.3 support. Until a new version is released, point your Gemfile at the repository:
|
|
33
|
+
>
|
|
34
|
+
> ```ruby
|
|
35
|
+
> # Gemfile
|
|
36
|
+
> gem "seams", github: "Davidslv/seams"
|
|
37
|
+
> ```
|
|
38
|
+
>
|
|
39
|
+
> Once a newer version is on RubyGems, use `gem "seams"` instead.
|
|
40
|
+
|
|
41
|
+
Then install the framework:
|
|
22
42
|
|
|
23
43
|
```bash
|
|
24
44
|
bundle install
|
|
25
45
|
bin/rails generate seams:install
|
|
26
|
-
bin/seams auth
|
|
27
|
-
bin/seams accounts
|
|
28
|
-
bin/seams notifications
|
|
29
|
-
bin/seams billing
|
|
30
|
-
bin/seams teams
|
|
31
|
-
bin/seams list
|
|
32
46
|
```
|
|
33
47
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
`seams:install` adds the framework files, a CI workflow, and a `bin/seams` command. Every step after this uses `bin/seams`.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
Generate the engines you need. The order matters, because later engines build on earlier ones:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bin/seams core # shared building blocks (always first)
|
|
56
|
+
bin/seams auth # sign-in, sessions, OAuth, API tokens
|
|
57
|
+
bin/seams accounts # the tenant (Account) and its members
|
|
58
|
+
bin/seams notifications # in-app, email, and SMS notifications
|
|
59
|
+
bin/seams billing # Stripe subscriptions
|
|
60
|
+
bin/seams teams # optional teams inside an account
|
|
61
|
+
bin/seams design --shell # UI components and an app layout
|
|
62
|
+
|
|
63
|
+
bundle install
|
|
64
|
+
bin/rails db:migrate
|
|
65
|
+
bin/seams list # show engines, their events, and subscribers
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> [!TIP]
|
|
69
|
+
> New to Seams? Follow **[Getting Started](doc/tutorials/GETTING_STARTED.md)**. It goes step by step from `bundle install` to a running app.
|
|
38
70
|
|
|
39
71
|
## What you get
|
|
40
72
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
### The engines
|
|
74
|
+
|
|
75
|
+
| Command | What it generates |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `bin/seams core` | Shared basics: per-request `Current` attributes, an audit log, tenant scoping, an email validator. |
|
|
78
|
+
| `bin/seams auth` | `Identity` (the person signing in), sessions, OAuth providers, API tokens. Personal data is encrypted at rest. |
|
|
79
|
+
| `bin/seams accounts` | `Account` (the tenant), memberships with roles, account scoping. |
|
|
80
|
+
| `bin/seams notifications` | Notifications over in-app, email, and SMS. Choose channels with `--channels in_app,email,sms` (default: all). |
|
|
81
|
+
| `bin/seams billing` | Stripe subscriptions using the official `stripe` gem (19.x), a webhook router with 13 handlers, and lifetime deals. |
|
|
82
|
+
| `bin/seams teams` | Teams, team memberships, and invitations. Choose features with `--with invitations,roles` (default: all). |
|
|
83
|
+
| `bin/seams admin` | An admin area built on Administrate and Pundit. Optional. See [Admin engine](#admin-engine) below. |
|
|
84
|
+
| `bin/seams design` | A design system: 33 `ui_*` components, Tailwind v4 theme tokens, a form builder, and a `/design/guide` gallery. `--shell` also adds an app layout and a starter dashboard. |
|
|
85
|
+
| `bin/seams permissions` | An editable map of which roles can do what, in `config/initializers/seams_permissions.rb`. See [Permissions](doc/reference/PERMISSIONS.md). |
|
|
86
|
+
|
|
87
|
+
### Framework and tools
|
|
88
|
+
|
|
89
|
+
| Command | What it does |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `bin/rails generate seams:install` | Installs the framework, the CI workflow, and `bin/seams`. |
|
|
92
|
+
| `bin/seams engine <name>` | Generates an empty engine for your own feature. |
|
|
93
|
+
| `bin/seams remove <name>` | Removes an engine, cleans up references to it, and adds a migration that drops its tables. |
|
|
94
|
+
| `bin/seams list` | Lists engines, the events they publish, and who subscribes to them. |
|
|
95
|
+
| `bin/seams test <engine>` | Runs one engine's tests. |
|
|
96
|
+
| `bin/seams quality <engine>` | Runs RuboCop on one engine. |
|
|
97
|
+
| `bin/seams resolve --eject <engine>/<file>` | Marks a generated file as yours, so future runs of the generator leave it alone. Also `--list-markers <engine>` and `--list-ejected`. |
|
|
98
|
+
| `bin/rails generate seams:auth:add_oauth_provider <name>` | Adds an OAuth provider to the auth engine. |
|
|
99
|
+
|
|
100
|
+
You also get:
|
|
101
|
+
|
|
102
|
+
- **Four custom RuboCop cops.** Two fail the build when one engine reaches into another's code or models. The other two check background job queue names and migration comments.
|
|
103
|
+
- **A GitHub Actions workflow** that runs each engine's tests in parallel.
|
|
104
|
+
- **A quality toolchain** set up by `seams:install`:
|
|
105
|
+
- strong_migrations and lefthook git hooks are on by default. Turn them off with `--no-strong-migrations` or `--no-lefthook`.
|
|
106
|
+
- herb (ERB lint) is off by default. Turn it on with `--herb`.
|
|
107
|
+
- **A Dockerfile and a Kamal `deploy.yml`**, written only if your app doesn't already have them. Rails 8 apps ship their own Dockerfile, which Seams leaves in place.
|
|
108
|
+
|
|
109
|
+
## Admin engine
|
|
110
|
+
|
|
111
|
+
`bin/seams admin` adds an admin area at `/admin`. It needs the `auth` engine. It adds the `administrate` and `pundit` gems to your Gemfile.
|
|
112
|
+
|
|
113
|
+
> [!IMPORTANT]
|
|
114
|
+
> **Only staff can open the admin area by default.** Set `staff: true` on an `Auth::Identity` to let that person in:
|
|
115
|
+
>
|
|
116
|
+
> ```ruby
|
|
117
|
+
> Auth::Identity.find_by(email: "you@example.com").update!(staff: true)
|
|
118
|
+
> ```
|
|
119
|
+
>
|
|
120
|
+
> Signed-out visitors are sent to the sign-in page. Signed-in identities that aren't staff get a 403.
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary><strong>Tenant mode: let each customer's admins manage their own account</strong></summary>
|
|
124
|
+
|
|
125
|
+
By default the admin area runs in **platform** mode: staff see every account. In **tenant** mode, an account's admins see only their own account's data.
|
|
126
|
+
|
|
127
|
+
Tenant mode needs three settings. Without the last two, tenant admins get a 403.
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
# config/initializers/seams_admin.rb
|
|
131
|
+
Seams::Admin.configure do |c|
|
|
132
|
+
c.tenancy_scope = :tenant
|
|
133
|
+
|
|
134
|
+
# Let any signed-in identity past the gate. The tenant policies then
|
|
135
|
+
# decide what each role may do.
|
|
136
|
+
c.authenticator = ->(ctrl) { ctrl.current_identity.present? }
|
|
137
|
+
|
|
138
|
+
# Tell the admin area which membership the request belongs to.
|
|
139
|
+
# This example takes the identity's first active membership. Use
|
|
140
|
+
# however your app picks the current account (subdomain, session, a switcher).
|
|
141
|
+
c.current_membership_resolver = lambda do |ctrl|
|
|
142
|
+
identity = ctrl.current_identity
|
|
143
|
+
identity && Accounts::Membership.find_by(identity_id: identity.id, active: true)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
With these set:
|
|
149
|
+
- An `admin` or `owner` of an account sees only that account's rows.
|
|
150
|
+
- Requests for another account's records return 404.
|
|
151
|
+
- A `member` gets a 403.
|
|
152
|
+
|
|
153
|
+
The generated `engines/admin/README.md` has the full reference.
|
|
154
|
+
|
|
155
|
+
</details>
|
|
156
|
+
|
|
157
|
+
## Upgrading an existing Seams app
|
|
158
|
+
|
|
159
|
+
Seams writes code into your app. So updating the gem does not change engines you already generated. Read the [CHANGELOG](CHANGELOG.md) before you update, and apply the changes that affect you.
|
|
160
|
+
|
|
161
|
+
> [!NOTE]
|
|
162
|
+
> If you generated engines before the September 2026 changes, check these:
|
|
163
|
+
> - **solid_cable 4.1 or newer:** each engine's test app needs `engines/*/spec/dummy/config/cable.yml`, containing `test:` with `adapter: test`. Without it, the engine's tests fail to load.
|
|
164
|
+
> - **Billing:** the generated code now targets `stripe ~> 19.0`. The generated services now raise `Billing::GatewayError` instead of `Stripe::*` errors. Set your Stripe webhook endpoint to the same API version as the gem.
|
|
165
|
+
> - **Admin:** regenerate the admin engine, or copy the fixes listed in the CHANGELOG. Earlier versions could not boot or serve any admin page.
|
|
166
|
+
>
|
|
167
|
+
> If you adopted Seams before Wave 9, start with the [Wave 8 upgrade guide](doc/how-to/UPGRADING_FROM_WAVE_8.md).
|
|
55
168
|
|
|
56
169
|
## Documentation
|
|
57
170
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- [
|
|
64
|
-
- [
|
|
65
|
-
- [
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
171
|
+
The **[documentation site](https://davidslv.github.io/seams/)** has every guide below, with search. The API reference is on **[rubydoc.info](https://rubydoc.info/gems/seams)**.
|
|
172
|
+
|
|
173
|
+
<details>
|
|
174
|
+
<summary><strong>Start here</strong></summary>
|
|
175
|
+
|
|
176
|
+
- [Getting Started](doc/tutorials/GETTING_STARTED.md): install, first engine, running app
|
|
177
|
+
- [Engine catalogue](doc/reference/ENGINE_CATALOGUE.md): every engine in detail
|
|
178
|
+
- [Architecture overview](doc/explanation/ARCHITECTURE.md): why Seams is built this way
|
|
179
|
+
|
|
180
|
+
</details>
|
|
181
|
+
|
|
182
|
+
<details>
|
|
183
|
+
<summary><strong>Building and extending</strong></summary>
|
|
184
|
+
|
|
185
|
+
- [Adding an engine](doc/how-to/ADDING_AN_ENGINE.md)
|
|
186
|
+
- [Removing an engine](doc/how-to/REMOVING_AN_ENGINE.md)
|
|
187
|
+
- [Writing an adapter](doc/how-to/WRITING_AN_ADAPTER.md): swap in Mailgun, Twilio, Paddle, and others
|
|
188
|
+
- [Writing follow-up generators](doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md)
|
|
189
|
+
- [Insertion points](doc/reference/INSERTION_POINTS.md) and the [catalogue of markers](doc/reference/INSERTION_POINTS_CATALOGUE.md)
|
|
190
|
+
- [Deploying](doc/how-to/DEPLOYING.md)
|
|
191
|
+
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
<details>
|
|
195
|
+
<summary><strong>Reference</strong></summary>
|
|
196
|
+
|
|
197
|
+
- [Current attributes](doc/reference/CURRENT_ATTRIBUTES.md): `Auth::Current`, `Accounts::Current`, `Teams::Current`, `Core::Current`
|
|
198
|
+
- [Permissions](doc/reference/PERMISSIONS.md): ability codes, roles, the grant map
|
|
199
|
+
- [Observability](doc/reference/OBSERVABILITY.md): logging, tracing, metrics
|
|
200
|
+
- [Testing](doc/reference/TESTING.md)
|
|
201
|
+
|
|
202
|
+
</details>
|
|
203
|
+
|
|
204
|
+
<details>
|
|
205
|
+
<summary><strong>Design system</strong></summary>
|
|
206
|
+
|
|
207
|
+
- [Design system overview](doc/design-system/DESIGN_SYSTEM.md) (start here)
|
|
208
|
+
- [Foundations](doc/design-system/DESIGN_SYSTEM_FOUNDATIONS.md): tokens and scales
|
|
209
|
+
- [Components](doc/design-system/DESIGN_SYSTEM_COMPONENTS.md): the 33 `ui_*` components
|
|
210
|
+
- [Forms](doc/design-system/DESIGN_SYSTEM_FORMS.md): `Design::FormBuilder`
|
|
211
|
+
- [Theming](doc/design-system/DESIGN_SYSTEM_THEMING.md)
|
|
212
|
+
- [Accessibility](doc/design-system/DESIGN_SYSTEM_ACCESSIBILITY.md)
|
|
213
|
+
|
|
214
|
+
</details>
|
|
215
|
+
|
|
216
|
+
<details>
|
|
217
|
+
<summary><strong>Architecture history</strong></summary>
|
|
218
|
+
|
|
219
|
+
- [Wave 9](doc/explanation/ARCHITECTURE_WAVE_9.md): the full system walk-through, including the identity / account / team split
|
|
220
|
+
- [Wave 10](doc/explanation/ARCHITECTURE_WAVE_10.md): insertion points, follow-up generators, the eject command
|
|
221
|
+
- [Wave 11](doc/explanation/ARCHITECTURE_WAVE_11.md): the admin engine
|
|
222
|
+
- [Personal data and GDPR](doc/explanation/WAVE_11_PII_GDPR.md)
|
|
223
|
+
- [Architecture Decision Records](doc/adr/)
|
|
224
|
+
|
|
225
|
+
</details>
|
|
226
|
+
|
|
227
|
+
## How Seams compares
|
|
228
|
+
|
|
229
|
+
| Instead of | Seams gives you |
|
|
76
230
|
| --- | --- |
|
|
77
|
-
| Bullet Train
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
engine and
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
[
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`bin/audit` before any push.
|
|
231
|
+
| Bullet Train or Jumpstart Pro | The code lives in your repo, not behind a gem. Seams also enforces engine boundaries with RuboCop cops. |
|
|
232
|
+
| `rails plugin new --mountable` | Engines come wired with events, registries, observability, boundary checks, and CI. |
|
|
233
|
+
| Microservices | One process and one deploy. No HTTP between services. Engines talk through synchronous events with explicit subscribers. |
|
|
234
|
+
|
|
235
|
+
## Project status
|
|
236
|
+
|
|
237
|
+
> [!NOTE]
|
|
238
|
+
> Seams is in active development and has not reached 1.0. Breaking changes are listed in the [CHANGELOG](CHANGELOG.md). Open work is tracked in [issue #5](https://github.com/Davidslv/seams/issues/5).
|
|
239
|
+
|
|
240
|
+
Each change is checked in CI:
|
|
241
|
+
- RuboCop, Brakeman, and bundle-audit
|
|
242
|
+
- the gem's own tests, on Ruby 3.3, 3.4, and 4.0.7
|
|
243
|
+
- an end-to-end run that creates a new Rails app with `rails new`, generates every engine, runs each engine's tests, and boots the app against Postgres
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
- **How to contribute:** [CONTRIBUTING.md](CONTRIBUTING.md) covers setup, the checks, and the pull request workflow. Run `bin/audit` before you push.
|
|
248
|
+
- **Security issues:** [SECURITY.md](SECURITY.md)
|
|
249
|
+
- **Getting help:** [SUPPORT.md](SUPPORT.md)
|
|
250
|
+
- **How the project is run:** [GOVERNANCE.md](GOVERNANCE.md) and [MAINTAINERS.md](MAINTAINERS.md)
|
|
251
|
+
- **Community expectations:** [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
252
|
+
- **AI coding agents:** [AGENTS.md](AGENTS.md) and [llms.txt](llms.txt)
|
|
253
|
+
- **Citing Seams:** [CITATION.cff](CITATION.cff)
|
|
101
254
|
|
|
102
255
|
## License
|
|
103
256
|
|
|
104
|
-
MIT
|
|
257
|
+
MIT. See [LICENSE](LICENSE).
|