Sifting through Fizzy’s pull requests, it was fascinating to see how the 37signals team thinks through problems, builds solutions, and ships software together. Outside of their massive contributions to open source libraries, the podcast and the books — this was a chance to see up close, with real code, the application building process.

Below you will read which 37signals team members owned which domains in this project, and which PRs best demonstrate their craft. If you’re new to the codebase (or want to level up in specific areas), I will point you to the PRs worth studying. Each section highlights an engineer’s domain knowledge and the code reviews that showcase their thinking.

Why Read Pull Requests?

Reading the code shows the outcome. Pull requests show the decisions.

I love reading PRs because they reveal the reasoning behind the work, the options considered and the tradeoffs made. Review comments often become real lessons as people question choices, suggest alternatives, and offer encouragement.

These reviews can create quick, informal mentorship where one person spots a cleaner abstraction and another catches an edge case before it slips through.

Follow a codebase through its PRs and you can see the product being built in real time as early spikes set patterns, refactors clear debt, and new features rise on earlier work.

Want to dive into a specific topic? Jump to the Learning Paths for curated PR sequences on AI/LLM integration, multi-tenancy with SQLite, Turbo/Hotwire, and more.


DHH (David Heinemeier Hansson)

Expertise: Rails Architecture, Code Style, API Design

DHH’s code reviews are masterclasses in Rails conventions. His 20 PRs and numerous reviews establish patterns the whole team follows. I believe this is the first publicly viewable application codebase where we see DHH doing code reviews — well worth the time to dig into those and see his thinking for yourself.

Must-Read PRs

PR #108 - Spike events system (reviewed)

This PR establishes core architectural patterns. Key feedback:

On delegated types and pagination:

“These threads need to be paginated, so you can’t do any in-memory sorting. This all needs to be converted to a delegated type, so you have a single table you can pull from.”

On lazy loading:

“Why not just delegate :user to :session? Then you get to lazy load it too.”

On naming conventions:

not_popped is pretty cumbersome of a word. Consider something like unpopped if staying in the negative or go with something like active.”

On test-induced design damage:

“I think that would then qualify as test-induced design damage. Better replace that with a mock or even better just a fixture session you can use. We should never let our desire for ease of testing bleed into the application itself.”

PR #120 - Use the new params#expect syntax

Demonstrates DHH’s preference for Rails’ built-in features:

“To ensure we don’t get spurious 500s when we can serve 400s on bad data.”

PR #119 - Start adding caching

Foundation for Fizzy’s caching strategy.

PR #425 - Plain text mentions (reviewed)

Key feedback on code style:

On method naming:

collect implies that we’re returning an array of mentions (as #collect). Would use create_mentions when you don’t care about the return value.”

On Rails built-ins:

“You can use after_save_commit instead of after_commit on: %i[ create update ].”

On API design:

“Bit too heavy-handed, imo. Better to make action return a StringInquirer. Then you can do event.action.completed?.”

DHH’s Style Patterns

  • Prefer delegation over accessor methods
  • Use Rails’ built-in features (StringInquirer, counter caches, after_save_commit)
  • Name methods by what they do, not what they return
  • Avoid test-induced design damage
  • Extract explaining methods for complex logic

Jorge Manrubia

Expertise: Turbo/Hotwire, Caching, AI/LLM Integration, ActiveRecord Patterns

With 285 PRs, Jorge is the most prolific backend contributor. His deep knowledge spans Hotwire, caching strategies, and AI integration.

Must-Read PRs

PR #339 - Enable page refreshes with morphing

Introduction of Turbo morphing for smoother updates:

“This makes the update actions on cards feel smoother.”

PR #483 - Fizzy Do with AI

A complete guide to integrating LLMs in Rails:

“Adds LLM processing to the Fizzy Do bar, enabling:

  • Filtering cards using natural language. E.g: cards assigned to jz.
  • Act on cards using natural language: E.g: close cards assigned to jz.
  • Get insight on cards: E.g: summarize this card.”

Key implementation details:

  • Using ruby_llm gem for model flexibility
  • Translating natural language to commands via structured output
  • Vector embeddings with SQLite-vec extension
  • Token limit handling

PR #1052 - Add more caching

HTTP caching and server-side template caching patterns:

“When rendering a day timeline, we:

  • HTTP cache the full request.
  • Cache the filter menu.
  • Cache the columns of events (so this could be shared across users who see the same set of events).

We leave out things like the day header, since this can vary on a per-user basis (e.g: timezones)”

PR #929 - Conversation cost limits (reviewed)

Jorge’s review demonstrates advanced patterns:

On extracting models:

“The shared param is often a smell that something is missing. My mind goes to having a new record Ai::Quota so that each user has a quota. The quota has an amount that gets reset weekly in a cron job.”

On custom ActiveRecord types:

“If you wanted to make an AR attribute, you can use a custom Active Model/Record type; there is first-class support for it. You can define a type, register it, and then declare attributes with it.”

On the .wrap convention:

“We have the internal in-house convention of using a static method .wrap when you want to get an object from several different types values.”

Jorge’s Patterns

  • Memoize expensive computations called multiple times per render
  • Use transient filters for temporary state
  • Consider cache keys carefully (user-specific vs. shared)
  • Extract concerns when responsibilities become clear
  • Use Stimulus values API over arbitrary data attributes

Mike Dalessio

Expertise: Infrastructure, Solid Queue, Dependencies, AI/Parsing, DevOps

With 317 PRs, flavorjones handles the infrastructure backbone: dependencies, background jobs, deployment, and parsing.

Must-Read PRs

PR #457 - Recursive descent parser for Fizzy Do

Hand-written parser design:

“Using a hand-written recursive descent parser instead of a LALR parser generator (like lex/yacc or rex/racc) because IMHO it’s easier to understand, extend, and debug. It also allows us to be a bit more error-tolerant where we need to.”

“The AST I’m introducing here is pretty simple, and I’m not tracking token location because my assumption is that the parsed strings are all going to be short.”

PR #159 - Introduce tenanting

Multi-tenant SQLite architecture with comprehensive documentation:

“This PR does a few notable things to implement a tenanted sqlite3 database for Fizzy:

  1. Introduce a Tenant model in a non-tenanted secondary database
  2. Tenant the ApplicationRecord models using active_record-tenanted
  3. Introduce middleware for selecting the tenant based on subdomain
  4. Extend the ‘first run’ flow to include a subdomain field
  5. Modify TestCase to handle parallel testing”

Design decisions documented:

  • Subdomain vs URL path tenanting
  • Slug separate from database name for changeability
  • SecureRandom for permanent database file names

PR #1109 - Yabeda for Solid Queue metrics

Monitoring background job infrastructure.

PR #501 - Stimulus controller for bubbles

Learning Stimulus through code review. Jorge’s feedback:

“Consider using Stimulus values instead of arbitrary data properties. It’s not a life-changing improvement, but nice to use what’s available and the API is nice. You would be able to do this.closeAtValue to read the attribute.”

“In javascript, our convention is closesAt instead of closes_at.”

flavorjones’s Patterns

  • Structured logging with tenant context
  • Careful dependency management (Rails edge testing)
  • Comprehensive test helpers for complex infrastructure
  • VCR cassettes for AI/external API testing
  • Clear PR descriptions explaining design decisions

Jason Zimdars

Expertise: UI/UX Design, CSS Architecture, ERB Templates, Filtering Systems

217 PRs focused on design implementation. JZ bridges design and code.

Must-Read PRs

JZ’s Patterns

  • CSS file structure by component
  • Visual documentation via screenshots in PRs

For more on Fizzy’s CSS approach, see my post Vanilla CSS Is All You Need.

Stanko Krtalic Rusendic

Expertise: Webhooks, Authentication, Security, External Integrations

76 PRs with deep focus on external system integration and security.

Must-Read PRs

PR #1083 - Webhooks

Comprehensive webhook implementation:

“Webhooks hook into the events model. For every event that’s created a WebhookDispatch job is spawned that checks which webhooks should get triggered.”

Security mechanisms:

  1. HMAC SHA256 signing for verification
  2. Timestamps to prevent replay attacks
  3. URL resolution checking for loopback/private networks (SSRF protection)
  4. Delinquent webhook auto-deactivation

“I used the limits concurrency method here to preserve causality as much as possible. It’s not a guarantee that the hooks will get delivered in order, but it helps preserve the order in most cases.”

PR #929 - Conversation cost limits

AI quota management with Money value object:

“We still aren’t sure how much Fizzy Ask is going to cost us, so to keep cost under control I’ve added a cost limit per conversation.”

Implementation details:

  • Quota tracking per user
  • Weekly reset periods
  • Money object for microcent calculations
  • Clear error messaging for users

Passwordless authentication with security considerations. Key discussion from flavorjones:

“One thing I’m nervous about here is the loss of authentication per account. It’s possible that an Identity (a person) would have multiple accounts under multiple email addresses… compromising one of those email addresses exposes all of those Fizzy accounts.”

monorkin’s Patterns

  • Security-first design (SSRF checks, signing, rate limits)
  • Detailed PR descriptions with implementation rationale
  • State machine patterns for deliveries (pending, in_progress, completed, errored)
  • Active Job Continuations for reliable delivery

Andy Rankin

Expertise: CSS/Styling, Stimulus Controllers, UI Components, Accessibility

With 268 PRs, Andy is the third most prolific contributor, focused entirely on frontend implementation.

Must-Read PRs

Andy’s Patterns

  • Component-focused CSS organization
  • Accessibility-first approach
  • localStorage for UI state persistence
  • Stimulus controllers for interactive behavior

Other Key Contributors

Jose Farias

Expertise: Filtering Systems, Form Handling, Early Architecture

23 PRs establishing core patterns in Fizzy’s early development.

Kevin McConnell

Expertise: Infrastructure, Notifications, Deployment, Performance

49 PRs focused on operational concerns.

Jeremy Daer

Expertise: Security, Ruby Infrastructure, DevOps

36 PRs with security focus.

Rosa Gutierrez

Expertise: CSRF Protection, Security Headers

Topic-Based Learning Paths

Turbo/Hotwire

  1. PR #339 - Enable page refreshes with morphing - Morphing basics
  2. PR #396 - Refresh timers when morphing happens - Handling side effects
  3. PR #416 - Use morphing in stream replace - Stream + morphing
  4. PR #490 - Refresh local time target when morphed - Component refresh patterns
  5. PR #696 - Turbo frames to avoid resetting forms - Turbo Frames for partial updates
  6. PR #1091 - Speed up filter expansion with turbo stream - Performance patterns
  7. PR #1327 - Fix morphing + pagination issues - Edge cases
  8. PR #1413 - Upgrade turbo - Upgrade patterns

Stimulus

  1. PR #501 - Stimulus controller for bubbles - Introduction with review feedback on values API
  2. PR #586 - Knobs - Custom input components
  3. PR #592 - Set value when using slider - State management
  4. PR #920 - Popup orientation - Positioning logic
  5. PR #962 - Combobox a11y - Accessibility patterns
  6. PR #1187 - Save expanded state to localStorage - Persistence
  7. PR #1199 - Column animation - Animation coordination
  8. PR #1936 - Built-in :self support - Modern Stimulus patterns

Rails Caching

  1. PR #119 - Start adding caching - Foundation
  2. PR #135 - Counter cache for comments - Counter caches
  3. PR #317 - Assignee dropdown cache bypass - Fragment cache invalidation
  4. PR #340 - Fix caching issue with cards - Debugging cache issues
  5. PR #566 - Invalidate card caches when editing workflows - Cross-model invalidation
  6. PR #1052 - Add more caching - HTTP + template caching
  7. PR #1132 - Fix caching issues + refactor - Systematic fixes
  8. PR #1377 - Extend public caching - CDN/proxy caching
  9. PR #1571 - HTTP caching for menus - Component caching
  10. PR #1607 - CSRF vs HTTP caching - When NOT to cache

AI/LLM Integration

  1. PR #457 - Recursive descent parser - Command parsing fundamentals
  2. PR #460 - Fizzy Do initial - Command system foundation
  3. PR #464 - Fizzy Do: confirmations, close cards - Action execution
  4. PR #466 - Fizzy Do: tags, help menu - Expanding commands
  5. PR #483 - Fizzy Do with AI - Complete LLM integration guide
  6. PR #857 - Fizzy Ask - AI assistant implementation
  7. PR #929 - Conversation cost limits - Cost management with Quota model
  8. PR #978 - Track costs of AI summaries - Usage tracking

Multi-tenancy (SQLite)

  1. PR #159 - Introduce tenanting - Full design discussion
  2. PR #168 - Tenanting attempt 2 - Iteration on design
  3. PR #279 - Tenanting v3 - Production implementation
  4. PR #283 - Migration script for multi-tenant - Data migration
  5. PR #311 - Extracted tenant resolver - Middleware patterns
  6. PR #372 - Scope data by account - Data isolation
  7. PR #403 - Tenanted db is the account scope - Architectural clarity
  8. PR #879 - Tenanted session token cookies - Session handling

Webhooks & External APIs

  1. PR #1083 - Webhooks - Complete webhook system with security
  2. PR #1161 - Custom labels for Webhooks - User customization
  3. PR #1169 - Separate auto-close from close events - Event granularity
  4. PR #1196 - Link unfurling - URL previews
  5. PR #1229 - Postpone as webhook trigger - Event expansion
  6. PR #1292 - Webhook cleanup recurring job - Maintenance

Security

  1. PR #1114 - Escape HTML everywhere - XSS prevention
  2. PR #1721 - Sec-Fetch-Site header reporting - Modern CSRF detection
  3. PR #1751 - Sec-Fetch-Site CSRF protection - Enforcement
  4. PR #1903 - DNS rebinding protection - Network security
  5. PR #1905 - Web Push SSRF - SSRF mitigation
  6. PR #1964 - Content Security Policy - CSP implementation
  7. PR #1083 - Webhook security - HMAC signing, SSRF protection, replay prevention

Notifications

  1. PR #199 - Notification spike - Foundation
  2. PR #208 - Notification index - UI structure
  3. PR #274 - Notification preferences - User preferences
  4. PR #306 - Quieter notifications - Noise reduction
  5. PR #405 - Refactor notifications - Clean architecture
  6. PR #425 - Plain text mentions - @mention notifications
  7. PR #475 - Broadcast notification readings - Real-time updates
  8. PR #974 - Bundled notification emails - Batching
  9. PR #1448 - Group notifications by card - Aggregation
  10. PR #1574 - Aggregate email notifications - Email digests

Background Jobs (Solid Queue)

  1. PR #469 - bin/dev ensures puma runs solid queue - Development setup
  2. PR #494 - Recurring job for unused tags - Cleanup jobs
  3. PR #559 - Mission control for jobs - Admin interface
  4. PR #943 - Recurring job to clean finished jobs - Maintenance
  5. PR #1109 - Yabeda for Solid Queue metrics - Monitoring
  6. PR #1290 - Match job workers to CPUs - Scaling
  7. PR #1329 - Performance tuning: jobs - Optimization
  8. PR #1664 - Jobs enqueued after transaction commit - Reliability
  9. PR #1924 - Retry mailer jobs on errors - Error handling
  1. PR #113 - Persisted filters - State management
  2. PR #115 - Move filtering to the model - Model layer
  3. PR #116 - BubbleFilter extraction - Service object
  4. PR #131 - New filtering UI - Interface design
  5. PR #138 - Filter chips as links - URL-based filters
  6. PR #265 - Quick filters - Shortcuts
  7. PR #567 - Revamp filter menu - Complete overhaul
  8. PR #624 - Render and filter tags/users in menu - Dynamic filtering

Active Storage & Attachments

  1. PR #328 - Move attachments from Account to Bubble - Model restructure
  2. PR #557 - Production mirrors to purestorage - Storage services
  3. PR #707 - Card attachments - UI implementation
  4. PR #767 - Preprocess image variants - Performance
  5. PR #770 - Call blob.preview for previewable attachments - Preview handling
  6. PR #773 - Fix slow uploads - Performance fix
  7. PR #941 - Turn off previews on large files - Resource management
  8. PR #1689 - Improve avatar image handling - Avatar processing

Action Text & Rich Content

  1. PR #560 - Drop action_text_markdowns table - Cleanup
  2. PR #564 - Autolink emails and URLs at render time - Content processing
  3. PR #873 - Sanitizer config for ActionText - Security config
  4. PR #912 - Fix rich text content not applied - Debugging
  5. PR #964 - Rename Action Text Lexical to Lexxy - Custom editor
  6. PR #1859 - Handle ill-formed remote images - Error handling

Real-time (Action Cable & Web Push)

  1. PR #475 - Broadcast notification readings - Broadcasting patterns
  2. PR #699 - Clean up cable meta tag - Setup
  3. PR #705 - Broadcast when notifications cleared - Event broadcasting
  4. PR #781 - Web push - Push notifications
  5. PR #1291 - Yabeda ActionCable metrics - Monitoring
  6. PR #1432 - Subscribe to page changes via turbo streams - Page subscriptions
  7. PR #1765 - Fix action cable error in OSS mode - Configuration
  8. PR #1800 - Scope broadcasts by account - Multi-tenant broadcasting
  9. PR #1810 - Disconnect action cable on user deactivation - Cleanup

Email (Action Mailer)

  1. PR #314 - Tenanted Action Mailer URL helpers - Multi-tenant setup
  2. PR #974 - Bundled notification emails - Batching
  3. PR #1067 - Mailer styles and type hierarchy - Email design
  4. PR #1326 - User timezone in notification emails - Timezone handling
  5. PR #1525 - SVG avatars in emails - Compatibility
  6. PR #1574 - Aggregate email notifications by card - Aggregation
  7. PR #1911 - Email delivery via env vars - Configuration
  8. PR #1924 - Retry mailer jobs on errors - Reliability

Performance Optimization

  1. PR #380 - Paginate cards - Pagination basics
  2. PR #773 - Fix slow uploads - Upload performance
  3. PR #1089 - Several performance optimizations - Multiple fixes
  4. PR #1129 - Improved performance of cleaning inaccessible data - Batch operations
  5. PR #1254 - Pagination improvements - Advanced pagination
  6. PR #1283 - Performance tuning round 1 - Systematic tuning
  7. PR #1329 - Performance tuning: jobs - Job optimization
  8. PR #1747 - Address N+1 query situations - Query optimization
  9. PR #1927 - Faster D&D with optimistic insertion - UI responsiveness

Observability & Monitoring

  1. PR #285 - Structured JSON logging - Logging setup
  2. PR #301 - Structured logs with tenant - Multi-tenant logging
  3. PR #472 - Log authenticated user - User context
  4. PR #1109 - Yabeda for Solid Queue - Job metrics
  5. PR #1112 - Yabeda for Puma - Server metrics
  6. PR #1118 - OTel collector for Prometheus - Metrics pipeline
  7. PR #1165 - More Yabeda modules - Extended monitoring
  8. PR #1291 - Yabeda ActionCable metrics - WebSocket monitoring
  9. PR #1602 - Logging tweaks - Log refinement
  10. PR #1834 - console1984 and audits1984 - Access auditing

Concern Extraction & Refactoring

  1. PR #116 - Pull out BubbleFilter - Service extraction
  2. PR #324 - Extract pagination controller - Controller extraction
  3. PR #346 - Refactor collections perma - View restructure
  4. PR #370 - Extract helper with fallback - Helper patterns
  5. PR #398 - Comments refactoring - Model cleanup
  6. PR #405 - Refactor notifications - Architecture cleanup
  7. PR #508 - Extract Card::Entropy concern - Concern extraction
  8. PR #985 - Separate method with two outputs - Method clarity
  9. PR #1105 - Extract proprietary integrations into engine - Engine extraction

Workflows & State Machines

  1. PR #121 - Spike workflows - Initial exploration
  2. PR #218 - Filtering by workflow stage - Stage filters
  3. PR #329 - Set buckets at workflow level - Workflow assignment
  4. PR #389 - Colors on stages - Visual differentiation
  5. PR #413 - Refactor workflow default stages - Default handling
  6. PR #662 - Stage command - Command interface
  7. PR #763 - Resolve stages - Stage resolution
  8. PR #1258 - Cleanup workflow/stages - Code cleanup

Auto-close & Entropy System

  1. PR #327 - Add staleness sort order - Staleness concept
  2. PR #436 - Configure autoclose period - User configuration
  3. PR #489 - Handle collections without auto-close - Edge cases
  4. PR #508 - Extract Card::Entropy concern - Concern extraction
  5. PR #585 - Entropy improvements - Refinement
  6. PR #591 - Invalidate cache on entropy config change - Cache coordination
  7. PR #1451 - Entropy::Configuration to Entropy - API cleanup

Keyboard Navigation & Accessibility

  1. PR #302 - First round of accessibility fixes - Foundation
  2. PR #537 - Prevent default on keyboard shortcuts - Shortcut handling
  3. PR #581 - Wire-up keyboard navigation - Navigation system
  4. PR #695 - Focus hover styles - Visual feedback
  5. PR #834 - Trays keyboard nav - Tray navigation
  6. PR #962 - Combobox a11y - Complex components
  7. PR #994 - Fix up hotkey labels - Label accuracy

Mobile & Responsive Design

  1. PR #480 - Responsive card view - Card responsiveness
  2. PR #597 - Responsive trays - Tray adaptation
  3. PR #604 - Mobile columns - Column layout
  4. PR #739 - Mobile insets - Safe areas
  5. PR #740 - Mobile card improvements - Touch optimization
  6. PR #778 - Smaller action size on mobile - Size adaptation
  7. PR #881 - Mobile workflow styles - Workflow on mobile
  8. PR #1208 - Dynamic height for pins based on viewport - Viewport handling

Watching & Subscriptions

  1. PR #310 - Collection notification settings - Preference foundation
  2. PR #1088 - Watchers - Watcher system
  3. PR #1099 - Watching polish - UI refinement
  4. PR #1228 - Fix watchers list caching - Cache issues
  5. PR #1231 - Fix watching card inconsistencies - State consistency
  6. PR #1239 - Higher fidelity watching/unwatching - Reliability
  7. PR #1432 - Subscribe to page changes via turbo streams - Real-time updates
  8. PR #1519 - Clean watchers when losing access - Data cleanup

Credentials & Configuration

  1. PR #554 - Create beta environment and move secrets - Rails credentials
  2. PR #584 - Introduce staging environment - Environment setup
  3. PR #647 - Don’t require encrypted credentials in test - Test isolation
  4. PR #863 - Dev env improvements for new accounts - Development DX
  5. PR #1911 - Email delivery via env vars - Env configuration
  6. PR #1976 - CSP gives env config precedence - Config override

Drag & Drop

  1. PR #207 - Drag bubble divider - Basic dragging
  2. PR #209 - Fix divider drag jankiness - Smooth interaction
  3. PR #607 - Drag and drop cards between stages - Card movement
  4. PR #1927 - Faster D&D with optimistic insertion - Performance