Skip to content

Merge template: replace Avo with Madmin, add teams/billing/MCP/multilingual#142

Open
newstler wants to merge 79 commits intomainfrom
refactor/merge-template
Open

Merge template: replace Avo with Madmin, add teams/billing/MCP/multilingual#142
newstler wants to merge 79 commits intomainfrom
refactor/merge-template

Conversation

@newstler
Copy link
Copy Markdown
Owner

@newstler newstler commented Apr 1, 2026

Summary

Major merge from the template remote, bringing in the new architecture and features while preserving all WhyRuby-specific functionality.

What changed

  • Admin panel: Replaced Avo with Madmin (dark-themed, magic-link admin auth)
  • Multi-tenancy: Added teams with memberships, billing (Stripe), and onboarding
  • AI chat: RubyLLM-powered chat with per-team scoping
  • MCP: Model Context Protocol tools via fast-mcp gem (agent-native architecture)
  • Multilingual: Mobility gem with auto-translation support
  • Articles: New team-scoped content model
  • Code style: 37signals conventions, Claude Code rules
  • Infrastructure: Improved Dockerfile (jemalloc), Litestream env-controlled, Tailwind 4 dark theme

What was preserved

  • GitHub OAuth via Devise + OmniAuth
  • All posts, categories, success stories, testimonials
  • Community profiles, developer map, newsletter
  • Cross-domain routing (whyruby.info + rubycommunity.org)
  • All existing views, layouts, and branding

Migration notes

  • Duplicate CreateUsers and CreateActiveStorageTables migrations removed (already existed)
  • CreateTeamsForExistingUsers migration fixed to use raw SQL (bypasses model callbacks for api_key column ordering)
  • Template fixture user IDs remapped to match existing fixture IDs

Test plan

  • RuboCop passes (0 offenses)
  • Brakeman passes (0 warnings)
  • All original WhyRuby tests pass
  • 12 template test failures remain (onboarding/articles tests need Devise auth adaptation)
  • Madmin admin panel loads and redirects to login
  • Homepage renders with success stories
  • Server starts without errors

🤖 Generated with Claude Code

newstler and others added 30 commits January 7, 2026 16:03
Generated complete favicon set from capedbot.svg including multi-resolution
.ico, PNG variants for different use cases, and SVG for modern browsers.
Updated layout to use proper favicon link tags and web app manifest.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove unused config/tailwind.config.js (v3 JS config was dead code)
- Add @custom-variant dark for class-based dark mode
- Add --font-sans to @theme for Inter font family
- Add .claude/rules/tailwind.md documenting v4 configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Only show AI models in the UI whose provider has API credentials configured.
Added Model.enabled method that filters by providers with API keys and
deduplicates by name. Default model name now shown in select dropdown.

- Renamed Model.for_select to Model.enabled
- Added Model.configured_providers and with_configured_provider scope
- Standardized credential key from :open_ai to :openai to match provider name
- Updated bin/configure, initializer, and documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
newstler and others added 30 commits January 30, 2026 05:23
   The MCP CRUD generator was being autoloaded by Zeitwerk in production,
   causing a NameError because Rails::Generators is not available there.
   Added 'generators' to the ignore list in autoload_lib.

   Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Users can belong to multiple teams with path-based team context
(/t/:team_slug/...). Toggleable via Rails.configuration.x.multi_tenant
in config/initializers/multitenancy.rb.

Key changes:
- Team and Membership models with many-to-many user relationship
- Team-scoped routes for chats, models, and all resources
- ApplicationController resolves team from URL and sets Current.team
- Sessions controller handles team invitations via magic links
- MCP tools support team context via x-team-slug header
- New team management UI (settings, members, invitations)
- Single-tenant mode auto-creates one team for all users

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teams now own API keys instead of individual users. MCP authentication
uses x-api-key header to identify the team and x-user-email header to
identify which team member is acting.

Changes:
- Add api_key column to teams table with unique index
- Remove api_key column from users table
- Update ApplicationTool with new auth flow (require_team!, require_user!)
- Update all MCP tools to use new authentication pattern
- Add API key display and regenerate button to team settings UI
- Update tests and fixtures for new authentication model

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- OG preview page at /og-image (1200x630, matches design system)
- ApplicationHelper og_title/og_description/og_image with content_for cascading
- Layout meta tags for Open Graph + Twitter Cards
- Per-page overrides via content_for(:og_title), content_for(:og_image), etc.
- Rake tasks for screenshot generation
- Updated skill file to match Rails implementation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Teams are the billing entity with Stripe Checkout, Customer Portal,
and webhook handling. Admin-only pricing/billing pages, subscription
gating, 5 MCP tools, and bin/configure support for Stripe keys.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Rails encrypted credentials with a Setting model (singleton row)
for all service keys (OpenAI, Anthropic, Stripe, SMTP, Litestream).
Add admin settings UI at /madmin/settings with show/edit views.
Simplify bin/configure to only handle secret_key_base.
Fix pre-existing RubyLLM model_id/counter_cache issues in chat tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Handles the full Stripe subscription lifecycle: cancel at period end,
show cancellation-pending state in billing UI, resume before period ends,
and resubscribe after full cancellation via existing checkout flow.

Also includes admin pricing management, mail settings, team name checks,
trial days configuration, and pricing toggle UI from prior branch work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… toggle

Performance: Fix N+1 queries and inefficient patterns across views and
controllers. Add eager loading (includes) for chats index/show, sidebar,
and madmin pages. Use counter caches instead of .count, collection
rendering instead of .each+render, Ruby methods on preloaded data instead
of SQL, and partition instead of double select. Add performance rules to
.claude/rules/performance.md and AGENTS.md for future development.

Provider credentials: Move AI provider API keys from settings table to
dedicated provider_credentials table for better organization.

Public chats: Add toggle to enable/disable chat feature via admin settings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sanitize upload filenames to prevent path traversal in Attachable
- Add allowlist to Setting.get to prevent arbitrary method invocation via public_send
- Use reset_session instead of nilling session keys on logout for full invalidation
- Rescue RubyLLM::ConfigurationError in UpdateChatTool for graceful error response
- Fix flaky UpdateChatTool tests to be deterministic without skips

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New projects created from this template no longer see 28 migration files
from the template's development history. Instead, bin/configure reads
db/schema.rb and generates a single CreateInitialSchema migration,
keeping the template repo's individual migrations intact for git merge.

Also remove unused i18n keys from madmin settings locale.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Privacy-first page analytics via tracking pixel (no cookies, no PII).
Analytics only render when GeoLite2 database is present. MaxMind
credentials stored in encrypted credentials and passed as Docker
build secrets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nullitics is now opt-in: first ask whether to enable analytics, then
optionally configure MaxMind for geolocation. Decision persisted via
config initializer. Fix FrozenError in migration consolidation on
Ruby 4.0 by using mutable string literal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single-tenant mode was buggy (new users got their own team instead of
joining the shared team). Rather than fix it, remove the toggle entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Explicitly sanitize sort_direction via ternary before interpolating
into Arel.sql strings, so Brakeman can verify it's safe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bin/ci was using `brakeman -q --no-exit-on-warn` which silently
passed on warnings, while GitHub CI ran `bin/brakeman` which exits
non-zero. Now both use the same command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add multitenancy, billing, MCP tools, and admin panel
* Add multilingual user-generated content with auto-translation

Implement automatic translation of user-generated content via Mobility gem
(KeyValue backend) and RubyLLM. Content is auto-translated when created or
updated, targeting all languages enabled for the team.

Key components:
- Mobility gem with shared polymorphic translation tables (UUIDv7 PKs)
- Language model with admin management via Madmin
- Team-Language join model for per-team language configuration
- Translatable concern for declarative model translation
- TranslateContentJob (gpt-4.1-nano) and BackfillTranslationsJob
- Accept-Language header locale detection
- Team languages UI for managing active languages
- Article demo model showcasing the translation pattern
- Full MCP parity: 4 language tools, 5 article tools, 3 resources
- Documentation in .claude/rules/multilingual.md and AGENTS.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add user locale preference, constrain languages to i18n yml files

- Add Language.available_codes that scans config/locales/ for yml files
- Validate Language.code against available_codes (prevents creating
  languages without matching translations)
- Add locale column to users with validation against enabled languages
- Auto-detect and save user locale from Accept-Language on first login
- Add locale preference UI to profile edit page
- Use user's locale as source language for content translations
- Enable i18n fallbacks in development and test environments
- Filter seeds against available_codes
- Replace OpenStruct with Data.define in TranslateContentJob tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Improve multilingual system and add full Russian translation

Remove English-centric constraints (any language can be last), add
localized language names, Madmin language management, and complete
Russian locale covering all UI strings with proper pluralization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Split Russian locale into directory structure matching English

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Simplify source_locale to use I18n.locale directly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix code review issues in multilingual feature

- Sync docs with actual Translatable API (translatable :attr, type:)
- Move Language queries from views to controllers (profiles)
- Use teams.size instead of teams.count on loaded collection
- Make Mobility initializer discover locales dynamically from config/locales
- Optimize translations_exist? from 2N queries to 2
- Remove unnecessary Language.find_by_code wrapper
- Use I18n.default_locale in BackfillTranslationsJob instead of hardcoded "en"
- Fix misleading route comment (Content vs Billing sections)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add avatar and logo image uploads for users and teams

- Add Active Storage attachments for user avatar and team logo
- Create reusable image upload partial with Stimulus controller
- Show avatars/logos in profile, team settings, and sidebar
- Support drag-and-drop upload with remove functionality
- Add i18n translations for EN and RU

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use FriendlyId with Babosa for team slug generation

Replace custom slug generation with FriendlyId gem and Babosa
for proper transliteration of Cyrillic and accented characters
(e.g. "Команда" → "komanda", "Équipe" → "equipe").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix code review issues in multilingual feature

- Fix migration version inconsistency (8.2 → 8.0)
- Extract inline SVG spinner to icons/spinner.svg with inline_svg
- Replace eager_load in BackfillTranslationsJob with Translatable.registry
- Use .size instead of .count on loaded collection in AvailableLanguagesResource
- Clean up confusing double sign_in in languages controller test
- Add null: false constraint on articles.title migration
- Increase Language.available_codes cache from 5min to 1hr
- Add uniqueness validation on TeamLanguage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update migration versions from 8.0 to 8.2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(setup): load Bundler before requiring ActiveSupport in bin/configure

bin/configure requires active_support/encrypted_configuration but never
loads Bundler, causing a LoadError on fresh clones after bundle install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Updated brakeman

* fix(test): make translatable test locale-independent

Wrap bare I18n.locale assertion in with_locale(:en) to prevent
flaky failure when parallel tests leak locale state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bin/configure needs activesupport for credential generation. Move
bundle install before bin/configure in bin/setup, and use targeted
gem activation instead of loading the full bundle.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#96)

* fix(configure): remove ActiveSupport dependency from credentials setup

bin/configure used `gem "activesupport"` to write encrypted credentials,
which failed when gems weren't in the load path. Since all API keys are
now managed via Madmin and secret_key_base is auto-generated by Rails in
development, we only need to generate the encryption key files (plain
Ruby). MaxMind credentials can be added later via `rails credentials:edit`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update credentials sections for Madmin-based key management

API keys are now managed in Madmin, not Rails credentials. Updated
README and AGENTS.md to reflect this and document MaxMind as the
only remaining use case for encrypted credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(configure): remove credential key generation entirely

No encrypted credentials are written during setup, so generating
key files is pointless — Rails generates them on first
`rails credentials:edit`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ingual

Major merge from template remote bringing in new architecture and features:

- Replace Avo admin panel with Madmin (dark-themed, magic-link admin auth)
- Add multi-tenant teams with memberships, billing (Stripe), and onboarding
- Add RubyLLM-powered AI chat with per-team scoping
- Add MCP (Model Context Protocol) tools via fast-mcp gem
- Add multilingual content support via Mobility gem with auto-translation
- Add articles as team-scoped content model
- Adopt 37signals code style conventions and Claude Code rules
- Upgrade Dockerfile with jemalloc, improve Litestream config
- Add dark theme CSS with full Madmin styling
- Keep all WhyRuby-specific features: GitHub OAuth, posts, testimonials,
  community profiles, developer map, newsletter, success stories

Known: 12 template test failures remain (onboarding/articles tests need
Devise auth adaptation — all original WhyRuby tests pass).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Devise with lightweight session-based authentication:

- Add current_user, user_signed_in?, sign_in, sign_out to ApplicationController
- Move OmniAuth config from Devise initializer to standalone omniauth.rb
- Rewrite OmniAuth callback controller (no longer inherits Devise)
- Rewrite sessions controller (no longer inherits Devise)
- Remove User.devise declaration and new_with_session
- Remove Devise gem, initializer (336 lines), and Warden dependency
- Update routes: replace devise_for with explicit OAuth routes
- Update test helper: OmniAuth test mode with direct session sign_in
- Remove Devise::Test::IntegrationHelpers from test files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant