Commit 6a04042
Merge template: replace Avo with Madmin, add teams/billing/MCP/multilingual (#142)
* Upgraded Ruby to 4.0.0
* Remove devise, add magick links, configure script, and CLAUDE.md
* Removed dotenv and added litestream
* Update documentation
* Add RubyLLM, fix Avo
* Replaced Avo with Madmin
* Style chats
* Implementing dark mode
* WIP: Fixing the design
* Fixed most of the UI
* Fixed most of the UI
* Fixed most of the UI
* Fixed most of the UI
* Add CapedBot favicon and web app icons
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>
* Seems fixed most of the UI
* Feature: Added Claude Configuration.
* Feature: Added Claude Configuration.
* Feature: Added AGENTS.md.
* Added TDD principles into AGENTS.md
* Added TDD principles into testing rule
* Added TDD principles into testing skill
* Added more skills and agents
* Refactor
* Style New Chat window
* Fix start chat UI
* Style New Chat window
* Converted all colors to OKLCH
* Migrate Tailwind config from v3 to v4 CSS-first approach
- 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>
* Filter models by configured provider credentials
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>
* Improve layout consistency and admin auth styling
- Add admin_auth layout for admin login pages
- Center content with mx-auto on home, chats, and dashboard pages
- Use shared flash partial in madmin layout for consistency
- Adjust admin login form background colors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fixed UI and added sorting on cost
* Add DDoS/malicious request protection and attachment display
- Add malicious path blocking middleware (WordPress, PHP, .env, .git, path traversal)
- Add rate limiting to auth controllers, chats, and messages using Rails 8 native rate_limit
- Add global rate limit (100 req/min) in ApplicationController
- Enable host authorization for production health checks
- Simplify Model.enabled scope to check credentials dynamically (no hardcoded provider list)
- Add image_processing gem and attachment display in chat messages with lightbox
- Add typing indicator controller
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor: Extract concerns, simplify dashboard, fix tests
- Extract Costable concern for shared cost formatting (User, Chat, Model)
- Extract Attachable concern for temp file handling (Chats, Messages)
- Simplify DashboardController with batch queries instead of N+1
- Restrict model refresh to admins only
- Fix fixtures with hardcoded ULIDs for circular FK handling
- Fix tests with missing assertions
- Delete unused CLAUDE.md.old backup file
- Add ulid gem for Ruby ULID generation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Consolidate CSS into single Tailwind entry point
- Merge stylesheets/application.css into tailwind/application.css
- Remove duplicate color definitions (now single source in @theme)
- Update var(--dark-*) to var(--color-dark-*) for consistency
- Fix Madmin layout loading CSS twice
- All layouts now use single "tailwind" stylesheet
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix favicon generation to preserve transparency
Update ImageMagick commands to use -background transparent before the
input file, which properly handles alpha channels especially for SVGs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor CSS to use native nesting syntax
Improves maintainability by nesting child selectors inside parents,
using & for pseudo-classes, and consolidating .dark theme rules.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add MCP server with Streamable HTTP transport
Integrate fast-mcp gem to expose all app functionality via Model
Context Protocol, making the app agent-native from day one.
- 12 MCP tools: chats (5), messages (2), models (3), users (2)
- 5 MCP resources with Mcp:: namespace (avoids Madmin conflicts)
- API key auth on users via x-api-key header
- Streamable HTTP transport patch for fast-mcp 1.6.0
- Custom generators: mcp:tool, mcp:crud, mcp:resource
- Parity rake task: bin/rails mcp:parity
- Full test coverage for all tools and resources
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Switched from ULID to UUIDv7
* Use concise id: hash syntax for UUIDv7 primary keys
Replace `id: false` + `t.primary_key` two-liner with the single-line
`id: { type: :string, default: -> { "uuid7()" } }` hash syntax across
all migrations, the generator template, and documentation.
Also fix Madmin refresh_all action by skipping set_record callback.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix duplicate counter cache callbacks and remove circular FK
- Remove manual increment/decrement callbacks in Chat since
belongs_to :model, counter_cache: :chats_count handles it
- Reorder Message methods so public API comes before private block
- Drop circular foreign key from messages to tool_calls while
keeping the column and index for RubyLLM
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Simplify deploy config to use Rails credentials
- Update Dockerfile for Ruby 4.0, add jemalloc/imagemagick
- Use credentials for SMTP and mailer settings instead of ENV vars
- bin/configure now writes to both dev and production credentials
- Only SECRET_KEY_BASE and SOLID_QUEUE_IN_PUMA remain as ENV vars
- .kamal/secrets reads from credentials via rails credentials:fetch
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Rubocop
* Move all user-facing text to Rails i18n
- Add i18n-tasks gem for translation management
- Enable raise_on_missing_translations in development
- Create locale file structure under config/locales/en/
- Replace ~85 hardcoded strings with t() calls in:
- Controllers (flash messages, auth errors)
- Mailers (subjects and email content)
- Views (sessions, home, chats, models, messages, shared)
- Add i18n health check to bin/ci (step 5/5)
- Add .claude/rules/i18n.md with conventions
Key conventions:
- Views use lazy lookup: t(".heading")
- Mailers use lazy lookup: t(".subject")
- Controllers use full paths: t("controllers.sessions.create.notice")
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add pre-commit hook to run bin/ci before commits
Uses Git's core.hooksPath to point to version-controlled bin/hooks
directory. Hook configuration is automatic via bin/setup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve configure script with port config, db:seed, and fixes
- Add development server port prompt (default 3000), updates Procfile.dev
and mailer config when non-default
- Run db:seed after bin/setup to create admin user
- Fix S3 bucket default to use project-name-backups (kebab-case)
- Fix CamelCase preservation (SailingPlus no longer becomes Sailingplus)
- Consolidate duplicate to_kebab_case into to_service_name
- Add comments explaining helper methods
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove server IP prompt, use domain for all deploy config
Domain serves as the server address in Kamal - no need for separate IP.
Simplified deploy.yml updates to single replace for app.example.com.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace app name in locales, layout, and manifests
Configure script now updates Template to project name in:
- config/locales/en.yml (app_name)
- app/views/layouts/application.html.erb (title, meta tags)
- app/views/pwa/manifest.json.erb
- public/site.webmanifest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Beautify bin/configure with Lipgloss terminal styling
Add colorful terminal UI using the lipgloss gem:
- Styled headers with rounded borders
- Color-coded sections (purple) and success messages (green)
- Cyan prompts with muted default value hints
- Summary box with green border
- Auto-installs lipgloss gem if not present
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix bin/configure
Simplify secrets: use RAILS_MASTER_KEY only
Remove redundant env var passing for secrets that can be read directly
from Rails credentials. Now only RAILS_MASTER_KEY is passed to the
container - everything else (SECRET_KEY_BASE, Litestream config) is
read from encrypted credentials at runtime.
- puma.rb: Check credentials directly for Litestream config
- deploy.yml: Only pass RAILS_MASTER_KEY as secret
- .kamal/secrets: Remove SECRET_KEY_BASE extraction
- Dockerfile: Update comment to show RAILS_MASTER_KEY
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Simplify bin/configure Litestream prompts and remove summary
- Remove default bucket name suggestion (user should enter explicitly)
- Simplify empty check condition
- Remove redundant summary box at end (info already shown during setup)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move mailer from_address from credentials to environment config
Non-sensitive configuration like email from address belongs in
environment config, not encrypted credentials.
- Add config.action_mailer.default_options in all environments
- Remove mailer from_address from credentials in bin/configure
- Update production.rb via bin/configure with user's from address
- Simplify ApplicationMailer (no longer reads from credentials)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improve bin/configure credential handling
- Add secret_prompt that masks entered values with stars
- Ask for separate dev/prod AI API keys (prod falls back to dev)
- SMTP and Litestream credentials only saved to production
- Use secret_prompt for all sensitive fields (passwords, API keys)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update AI API Keys section description in bin/configure
Clarify that RubyLLM supports many providers but only OpenAI/Anthropic
are pre-configured here. Add TODO for future improvement to allow
selecting from all supported providers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add auto-commit after bin/configure completes
Automatically commits all configuration changes at the end of setup
with a message including project name, domain, and admin email.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor setup flow: bin/setup calls bin/configure
- bin/setup now calls bin/configure on first run (if .configured missing)
- bin/configure no longer calls bin/setup (avoids circular dependency)
- Added git remote handling: renames origin to template, asks for user repo
- Moved db:seed from configure to setup
- Updated README to reflect new workflow (just run bin/setup)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix duplicate db:seed and add template update tip
- Remove db:seed from bin/setup (db:prepare already seeds new DB)
- Add hot pink tip after git remote setup explaining how to pull
future updates from the template remote
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Unify color system in bin/configure
- Define all colors as hex constants (GOLD, YELLOW added)
- Add ansi_color() helper to convert hex to ANSI true color
- Typing input: YELLOW (#FFFF00)
- Confirmed input: GOLD (#FFD700)
- Consistent color definitions throughout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: Exclude generators from autoload in production
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>
* Add multitenancy with team-scoped routes
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>
* Move MCP API keys from users to teams
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>
* Add Open Graph image system with per-page meta tag support
- 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>
* Add Stripe billing integration for team subscriptions
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>
* f
* Move service API keys from credentials to database-backed admin settings
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>
* Multitenancy.
* Add subscription cancellation, resume, and resubscription lifecycle
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>
* Add performance optimizations, provider credentials, and public chats 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>
* Fix critical security issues and handle provider config errors
- 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>
* bundle update
* Move mail from setting from configure script to UI
* Styled madmin
* Styled show pages
* Consolidate migrations into single initial schema during bin/configure
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>
* Add Nullitics analytics with MaxMind GeoLite2 geolocation
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>
* Fix configure script: two-step analytics flow and frozen string bug
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>
* Update README: AI-Native Rails Template with featured AI capabilities
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Nullitics analytics to README tech stack
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove single-tenant mode, always multi-tenant
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>
* Fix Brakeman SQL injection warnings in Madmin controllers
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>
* Use bin/brakeman in CI so warnings fail the build
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 multilingual content with auto-translation and Russian locale (#92)
* 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 bin/setup LoadError for ActiveSupport (#93)
* 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>
* fix(setup): install gems before running configure (#95)
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>
* fix(configure): remove ActiveSupport dependency from credentials setup (#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>
* refactor(auth): remove Devise, use session-based GitHub OAuth directly
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>
* fix(deploy): improve credential reading and build reliability (#97)
- Replace `rails runner` with `credentials:show` pipe in .kamal/secrets
to avoid booting the full Rails app for credential extraction
- Save GeoLite2 download to temp file and make failure non-fatal so
builds succeed even when MaxMind is unavailable
- Reconfigure RubyLLM credentials before each job so worker processes
pick up credentials saved by the web process after boot
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(deps): update all gems, fix ActiveSupport::Configurable deprecation
- Upgrade omniauth-rails_csrf_protection 1.0.2 → 2.0.1 (fixes
ActiveSupport::Configurable deprecation warning on Rails 8.1+)
- Update ruby_llm, lefthook, regexp_parser, parallel, addressable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add design spec for 37signals refactor + RubyLLM migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add implementation plan for 37signals refactor + RubyLLM migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move LocationNormalizer + TimezoneResolver to User::Geocodable concern
Merge LocationNormalizer and TimezoneResolver service objects into a
User::Geocodable concern with a single geocode! method. Thin out
NormalizeLocationJob to delegate to user.geocode!. Move tests to
test/models/concerns/user/geocodable_test.rb and delete old service files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move SvgSanitizer to Post::SvgSanitizable concern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move MetadataFetcher to Post::MetadataFetchable concern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move ImageProcessor to Post::ImageVariantable concern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move SuccessStoryImageGenerator to Post::OgImageGeneratable concern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move GithubDataFetcher to User::GithubSyncable concern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add purpose column to chats for system AI tracking
* refactor: move summary generation to Post::AiSummarizable, use RubyLLM
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move testimonial generation to Testimonial::AiGeneratable, rename job
Extracts AI field generation logic from GenerateTestimonialFieldsJob into
Testimonial::AiGeneratable concern using RubyLLM. Renames job to GenerateTestimonialJob.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move testimonial validation to Testimonial::AiValidatable, use RubyLLM
Extracts AI validation logic from ValidateTestimonialJob into
Testimonial::AiValidatable concern using RubyLLM. Thins out the job to a single
delegation call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove ruby-openai and anthropic gems, RubyLLM handles all AI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: rewrite AGENTS.md to reflect post-refactor architecture
Update the AI agent guide to accurately describe the 37signals vanilla
Rails style: fat models with concerns, no service objects. Add Concern
Catalog, AI Operations section, and thin-delegator job pattern. Remove
all references to deleted services and old gems.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clean up extra blank lines in post.rb, fix stale job name in README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract UsersController map_data and og_image to nested resource controllers
Move map_data to Users::MapDataController#show and og_image to Users::OgImagesController#show, following 37signals REST conventions. UsersController now only contains index and show.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: extract Tags, Sessions, and Teams::Settings custom actions to nested resource controllers
- Tags#search → Tags::SearchesController#show (GET /tags/search unchanged)
- Sessions#verify → Sessions::VerificationsController#show, adds missing GET /auth/:token route
- Admins::Sessions#verify → Admins::Sessions::VerificationsController#show
- Teams::Settings#regenerate_api_key → Teams::Settings::ApiKeyRegenerationsController#create (PATCH→POST)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: extract PostsController custom actions to nested resource controllers
Move image, preview, fetch_metadata, check_duplicate_url to:
- Posts::ImagesController#show
- Posts::PreviewsController#create
- Posts::MetadataController#create
- Posts::DuplicateChecksController#create
PostsController now only has REST actions: show, new, create, edit, update, destroy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract UserSettingsController to nested REST resource controllers
Replace toggle actions with proper REST resources:
- UserSettings::VisibilitiesController#update (was toggle_public)
- UserSettings::OpenToWorksController#update (was toggle_open_to_work)
- UserSettings::NewslettersController#update (was toggle_newsletter)
- UserSettings::RepositoryHidesController#create/#destroy (was hide_repo/unhide_repo)
Delete UserSettingsController entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: remove 98 useless tests that just verify Rails framework behavior
Delete tests that only check validates/associations/scopes/formatting
without testing actual business logic. Removed:
- 8 entire model test files (article, chat, membership, star_snapshot,
price, tool_call, model, admin)
- 8 controller test files (home, articles, profiles, teams CRUD,
billing, checkouts, pricing, members)
- 4 resource test files (MCP redirect stubs)
- 3 job test files (enqueue-only tests)
- Trimmed team_test, testimonial_test, message_test to keep only
business logic tests
376 → 278 tests. All remaining tests verify actual app behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add User trust, profile visibility, SVG/metadata/AI concern tests
- Add trusted? threshold tests to user_test.rb (scope + instance method)
- Add UsersController tests for public/private profile visibility
- Add Post::SvgSanitizable class method tests for XSS sanitization
- Add Post::MetadataFetchable tests with WebMock for HTTP behavior
- Add Testimonial::AiGeneratable tests for heading_taken? logic
- Fix Project#topics to always return Array (fixture/JSON column mismatch)
- Fix projects.yml fixture topics to use empty JSON arrays
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: guard post_path_for against nil category to prevent homepage crash
Posts without a valid category association would crash the homepage with
ActionController::UrlGenerationError. Now falls back to root_path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(madmin): expand admin panel with full resource CRUD and improved user views
Add Madmin resources, controllers, and views for Categories, Comments, Posts,
Projects, Reports, Tags, and Testimonials. Enhance user index/show with
content stats, filtering by role/trusted status, and company-team linking.
Add team consolidation rake task and sidebar navigation for all resources.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(madmin): update dashboard cards with projects, testimonials, and reordered metrics
Replace teams/messages cards with projects and testimonials stats.
Reorder dashboard cards to prioritize content metrics over infrastructure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(routes): cross-domain auth broken by wildcard route matching /auth/receive
The wildcard `get "auth/:token"` route was matching `/auth/receive` before
the explicit route, sending cross-domain auth requests to the wrong controller.
Moved explicit auth routes above the wildcard. Also fixed undefined
`new_session_path` in VerificationsController.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(settings): move credentials and AI models from code to Madmin Settings
Move all credentials (except maxmind) from Rails encrypted credentials
to the Madmin Settings admin panel, and add per-task AI model configuration.
- Add GitHub OAuth keys (whyruby + rubycommunity) to Settings
- Add GitHub API token to Settings
- Add default_ai_model and per-task models (summary, testimonial, validation, translation)
- Remove hardcoded SMTP from production.rb (Setting#configure_smtp! handles it)
- Fix mask_secret helper by moving to Madmin::ApplicationHelper
- Update omniauth initializer to read from Settings
- Update GithubSyncable to read API token from Settings
- Update all AI concerns and TranslateContentJob to use per-task models
- Update Madmin settings UI with GitHub, AI Models sections
- Update rake tasks to use Settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(urls): allow dots in URL paths and prevent bio overflow
- Update URL regex to allow dots mid-path while still excluding trailing dots
- Add break-all to user tile bio to prevent long text from overflowing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ui): use break-words instead of break-all for user bio text
break-words is less aggressive and only breaks at word boundaries when
needed, preserving readability while still preventing overflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>1 parent 2a01b93 commit 6a04042
622 files changed
Lines changed: 26356 additions & 5948 deletions
File tree
- .claude
- agents
- commands
- rules
- skills/og-image
- .cursor/rules
- .kamal
- app
- assets
- images
- icons
- tailwind
- avo
- actions
- resources
- controllers
- admins
- sessions
- avo
- concerns
- madmin
- active_storage
- settings
- models
- posts
- sessions
- tags
- teams
- settings
- user_settings
- users
- webhooks
- helpers
- admins
- madmin
- javascript/controllers
- jobs
- madmin
- fields
- resources
- active_storage
- mailers
- models
- concerns
- post
- testimonial
- user
- resources
- mcp
- services
- tools
- articles
- billing
- chats
- languages
- messages
- models
- teams
- users
- views
- admin_mailer
- admins/sessions
- articles
- chats
- layouts
- madmin
- madmin
- active_storage
- attachments
- blobs
- variant_records
- admins
- application
- categories
- chats
- comments
- dashboard
- fields
- gravatar_field
- json_field
- languages
- mail
- messages
- models
- posts
- prices
- projects
- providers
- reports
- settings
- ai_models
- tags
- teams
- testimonials
- tool_calls
- users
- messages
- models
- og_images
- onboardings
- profiles
- sessions
- teams
- billing
- languages
- members
- pricing
- settings
- user_mailer
- users
- og_images
- bin
- hooks
- config
- credentials
- environments
- initializers
- locales
- en
- views
- admins
- madmin
- ru
- views
- admins
- madmin
- routes
- db
- migrate
- docs/superpowers
- plans
- specs
- lib
- generators/mcp
- crud
- templates
- resource
- templates
- tool
- templates
- middleware
- tasks
- templates/active_record/migration
- public
- test
- controllers
- teams
- webhooks
- fixtures
- jobs
- mailers/previews
- middleware
- models
- concerns
- post
- testimonial
- user
- resources
- services
- tools
- articles
- billing
- chats
- languages
- messages
- models
- users
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
116 | | - | |
| 116 | + | |
117 | 117 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
0 commit comments