Fizzy’s Pull Requests: Who Built What and How
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. 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. 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. 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: “ is pretty cumbersome of a word. Consider something like if staying in the negative or go with something like .” 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.” 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.” Foundation for Fizzy’s caching strategy. Key feedback on code style: On method naming: “ implies that we’re returning an array of mentions (as #collect). Would use when you don’t care about the return value.” On Rails built-ins: “You can use instead of .” On API design: “Bit too heavy-handed, imo. Better to make action return a StringInquirer. Then you can do .” 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. Introduction of Turbo morphing for smoother updates: “This makes the update actions on cards feel smoother.” A complete guide to integrating LLMs in Rails: “Adds LLM processing to the Fizzy Do bar, enabling: Key implementation details: HTTP caching and server-side template caching patterns: “When rendering a day timeline, we: We leave out things like the day header, since this can vary on a per-user basis (e.g: timezones)” 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 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 convention: “We have the internal in-house convention of using a static method when you want to get an object from several different types values.” Expertise: Infrastructure, Solid Queue, Dependencies, AI/Parsing, DevOps With 317 PRs, flavorjones handles the infrastructure backbone: dependencies, background jobs, deployment, and parsing. 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.” Multi-tenant SQLite architecture with comprehensive documentation: “This PR does a few notable things to implement a tenanted sqlite3 database for Fizzy: Design decisions documented: Monitoring background job infrastructure. 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 to read the attribute.” “In javascript, our convention is instead of .” Expertise: UI/UX Design, CSS Architecture, ERB Templates, Filtering Systems 217 PRs focused on design implementation. JZ bridges design and code. For more on Fizzy’s CSS approach, see my post Vanilla CSS Is All You Need . Expertise: Webhooks, Authentication, Security, External Integrations 76 PRs with deep focus on external system integration and security. 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: “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.” 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: 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.” Expertise: CSS/Styling, Stimulus Controllers, UI Components, Accessibility With 268 PRs, Andy is the third most prolific contributor, focused entirely on frontend implementation. Expertise: Filtering Systems, Form Handling, Early Architecture 23 PRs establishing core patterns in Fizzy’s early development. Expertise: Infrastructure, Notifications, Deployment, Performance 49 PRs focused on operational concerns. Expertise: Security, Ruby Infrastructure, DevOps 36 PRs with security focus. Expertise: CSRF Protection, Security Headers Prefer delegation over accessor methods Use Rails’ built-in features (StringInquirer, counter caches, ) Name methods by what they do, not what they return Avoid test-induced design damage Extract explaining methods for complex logic Filtering cards using natural language. E.g: . Act on cards using natural language: E.g: . Get insight on cards: E.g: .” Using gem for model flexibility Translating natural language to commands via structured output Vector embeddings with SQLite-vec extension Token limit handling 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). 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 Introduce a model in a non-tenanted secondary database Tenant the ApplicationRecord models using active_record-tenanted Introduce middleware for selecting the tenant based on subdomain Extend the ‘first run’ flow to include a subdomain field Modify to handle parallel testing” Subdomain vs URL path tenanting Slug separate from database name for changeability SecureRandom for permanent database file names 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 PR #305 - New visual design - Complete UI overhaul that defined Fizzy’s visual language PR #131 - New filtering UI - Filter interface patterns PR #335 - Two column Collection design - Layout architecture decisions PR #265 - Quick filters - User-facing filter shortcuts PR #608 - Add multiple cards quickly - Rapid input UX patterns CSS file structure by component Visual documentation via screenshots in PRs HMAC SHA256 signing for verification Timestamps to prevent replay attacks URL resolution checking for loopback/private networks (SSRF protection) Delinquent webhook auto-deactivation Quota tracking per user Weekly reset periods Money object for microcent calculations Clear error messaging for users 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 PR #962 - Combobox a11y - Accessibility patterns for complex components PR #920 - Popup orientation - Positioning logic for popups PR #1199 - Column animation - CSS animation patterns PR #1187 - Save expanded state to localStorage - Client-side state persistence PR #836 - Lightbox improvements - Image viewing UX PR #379 - Btn dropdown - Dropdown component patterns Component-focused CSS organization Accessibility-first approach localStorage for UI state persistence Stimulus controllers for interactive behavior PR #113 - Persisted filters - Filter state management PR #124 - Build threads from a single table - Delegated types implementation PR #135 - Counter cache for comments - ActiveRecord counter cache patterns PR #199 - Notification spike - Notification system foundation PR #274 - Notification preferences - User preference architecture PR #1377 - Extend public caching to 30 seconds - HTTP caching for CDN optimization PR #1903 - Security: DNS rebinding protection - Closing security vulnerabilities PR #1905 - Security: Web Push SSRF - SSRF mitigation patterns PR #1964 - Content Security Policy - CSP implementation PR #1721 - Sec-Fetch-Site header reporting - Modern browser security header usage PR #1751 - Sec-Fetch-Site CSRF protection - Moving from report-only to enforcement PR #339 - Enable page refreshes with morphing - Morphing basics PR #396 - Refresh timers when morphing happens - Handling side effects PR #416 - Use morphing in stream replace - Stream + morphing PR #490 - Refresh local time target when morphed - Component refresh patterns PR #696 - Turbo frames to avoid resetting forms - Turbo Frames for partial updates PR #1091 - Speed up filter expansion with turbo stream - Performance patterns PR #1327 - Fix morphing + pagination issues - Edge cases PR #1413 - Upgrade turbo - Upgrade patterns PR #501 - Stimulus controller for bubbles - Introduction with review feedback on values API PR #586 - Knobs - Custom input components PR #592 - Set value when using slider - State management PR #920 - Popup orientation - Positioning logic PR #962 - Combobox a11y - Accessibility patterns PR #1187 - Save expanded state to localStorage - Persistence PR #1199 - Column animation - Animation coordination PR #1936 - Built-in :self support - Modern Stimulus patterns PR #119 - Start adding caching - Foundation PR #135 - Counter cache for comments - Counter caches PR #317 - Assignee dropdown cache bypass - Fragment cache invalidation PR #340 - Fix caching issue with cards - Debugging cache issues PR #566 - Invalidate card caches when editing workflows - Cross-model invalidation PR #1052 - Add more caching - HTTP + template caching PR #1132 - Fix caching issues + refactor - Systematic fixes PR #1377 - Extend public caching - CDN/proxy caching PR #1571 - HTTP caching for menus - Component caching PR #1607 - CSRF vs HTTP caching - When NOT to cache PR #457 - Recursive descent parser - Command parsing fundamentals PR #460 - Fizzy Do initial - Command system foundation PR #464 - Fizzy Do: confirmations, close cards - Action execution PR #466 - Fizzy Do: tags, help menu - Expanding commands PR #483 - Fizzy Do with AI - Complete LLM integration guide PR #857 - Fizzy Ask - AI assistant implementation PR #929 - Conversation cost limits - Cost management with Quota model PR #978 - Track costs of AI summaries - Usage tracking PR #159 - Introduce tenanting - Full design discussion PR #168 - Tenanting attempt 2 - Iteration on design PR #279 - Tenanting v3 - Production implementation PR #283 - Migration script for multi-tenant - Data migration PR #311 - Extracted tenant resolver - Middleware patterns PR #372 - Scope data by account - Data isolation PR #403 - Tenanted db is the account scope - Architectural clarity PR #879 - Tenanted session token cookies - Session handling PR #1083 - Webhooks - Complete webhook system with security PR #1161 - Custom labels for Webhooks - User customization PR #1169 - Separate auto-close from close events - Event granularity PR #1196 - Link unfurling - URL previews PR #1229 - Postpone as webhook trigger - Event expansion PR #1292 - Webhook cleanup recurring job - Maintenance PR #1114 - Escape HTML everywhere - XSS prevention PR #1721 - Sec-Fetch-Site header reporting - Modern CSRF detection PR #1751 - Sec-Fetch-Site CSRF protection - Enforcement PR #1903 - DNS rebinding protection - Network security PR #1905 - Web Push SSRF - SSRF mitigation PR #1964 - Content Security Policy - CSP implementation PR #1083 - Webhook security - HMAC signing, SSRF protection, replay prevention PR #199 - Notification spike - Foundation PR #208 - Notification index - UI structure PR #274 - Notification preferences - User preferences PR #306 - Quieter notifications - Noise reduction PR #405 - Refactor notifications - Clean architecture PR #425 - Plain text mentions - @mention notifications PR #475 - Broadcast notification readings - Real-time updates PR #974 - Bundled notification emails - Batching PR #1448 - Group notifications by card - Aggregation PR #1574 - Aggregate email notifications - Email digests PR #469 - bin/dev ensures puma runs solid queue - Development setup PR #494 - Recurring job for unused tags - Cleanup jobs PR #559 - Mission control for jobs - Admin interface PR #943 - Recurring job to clean finished jobs - Maintenance PR #1109 - Yabeda for Solid Queue metrics - Monitoring PR #1290 - Match job workers to CPUs - Scaling PR #1329 - Performance tuning: jobs - Optimization PR #1664 - Jobs enqueued after transaction commit - Reliability PR #1924 - Retry mailer jobs on errors - Error handling PR #113 - Persisted filters - State management PR #115 - Move filtering to the model - Model layer PR #116 - BubbleFilter extraction - Service object PR #131 - New filtering UI - Interface design PR #138 - Filter chips as links - URL-based filters PR #265 - Quick filters - Shortcuts PR #567 - Revamp filter menu - Complete overhaul PR #624 - Render and filter tags/users in menu - Dynamic filtering PR #328 - Move attachments from Account to Bubble - Model restructure PR #557 - Production mirrors to purestorage - Storage services PR #707 - Card attachments - UI implementation PR #767 - Preprocess image variants - Performance PR #770 - Call blob.preview for previewable attachments - Preview handling PR #773 - Fix slow uploads - Performance fix PR #941 - Turn off previews on large files - Resource management PR #1689 - Improve avatar image handling - Avatar processing PR #560 - Drop action_text_markdowns table - Cleanup PR #564 - Autolink emails and URLs at render time - Content processing PR #873 - Sanitizer config for ActionText - Security config PR #912 - Fix rich text content not applied - Debugging PR #964 - Rename Action Text Lexical to Lexxy - Custom editor PR #1859 - Handle ill-formed remote images - Error handling PR #475 - Broadcast notification readings - Broadcasting patterns PR #699 - Clean up cable meta tag - Setup PR #705 - Broadcast when notifications cleared - Event broadcasting PR #781 - Web push - Push notifications PR #1291 - Yabeda ActionCable metrics - Monitoring PR #1432 - Subscribe to page changes via turbo streams - Page subscriptions PR #1765 - Fix action cable error in OSS mode - Configuration PR #1800 - Scope broadcasts by account - Multi-tenant broadcasting PR #1810 - Disconnect action cable on user deactivation - Cleanup PR #314 - Tenanted Action Mailer URL helpers - Multi-tenant setup PR #974 - Bundled notification emails - Batching PR #1067 - Mailer styles and type hierarchy - Email design PR #1326 - User timezone in notification emails - Timezone handling PR #1525 - SVG avatars in emails - Compatibility PR #1574 - Aggregate email notifications by card - Aggregation PR #1911 - Email delivery via env vars - Configuration PR #1924 - Retry mailer jobs on errors - Reliability PR #380 - Paginate cards - Pagination basics PR #773 - Fix slow uploads - Upload performance PR #1089 - Several performance optimizations - Multiple fixes PR #1129 - Improved performance of cleaning inaccessible data - Batch operations PR #1254 - Pagination improvements - Advanced pagination PR #1283 - Performance tuning round 1 - Systematic tuning PR #1329 - Performance tuning: jobs - Job optimization PR #1747 - Address N+1 query situations - Query optimization PR #1927 - Faster D&D with optimistic insertion - UI responsiveness PR #285 - Structured JSON logging - Logging setup PR #301 - Structured logs with tenant - Multi-tenant logging PR #472 - Log authenticated user - User context PR #1109 - Yabeda for Solid Queue - Job metrics PR #1112 - Yabeda for Puma - Server metrics PR #1118 - OTel collector for Prometheus - Metrics pipeline PR #1165 - More Yabeda modules - Extended monitoring PR #1291 - Yabeda ActionCable metrics - WebSocket monitoring PR #1602 - Logging tweaks - Log refinement PR #1834 - console1984 and audits1984 - Access auditing PR #116 - Pull out BubbleFilter - Service extraction PR #324 - Extract pagination controller - Controller extraction PR #346 - Refactor collections perma - View restructure PR #370 - Extract helper with fallback - Helper patterns PR #398 - Comments refactoring - Model cleanup PR #405 - Refactor notifications - Architecture cleanup PR #508 - Extract Card::Entropy concern - Concern extraction PR #985 - Separate method with two outputs - Method clarity PR #1105 - Extract proprietary integrations into engine - Engine extraction PR #121 - Spike workflows - Initial exploration PR #218 - Filtering by workflow stage - Stage filters PR #329 - Set buckets at workflow level - Workflow assignment PR #389 - Colors on stages - Visual differentiation PR #413 - Refactor workflow default stages - Default handling PR #662 - Stage command - Command interface PR #763 - Resolve stages - Stage resolution PR #1258 - Cleanup workflow/stages - Code cleanup PR #327 - Add staleness sort order - Staleness concept PR #436 - Configure autoclose period - User configuration PR #489 - Handle collections without auto-close - Edge cases PR #508 - Extract Card::Entropy concern - Concern extraction PR #585 - Entropy improvements - Refinement PR #591 - Invalidate cache on entropy config change - Cache coordination PR #1451 - Entropy::Configuration to Entropy - API cleanup PR #302 - First round of accessibility fixes - Foundation PR #537 - Prevent default on keyboard shortcuts - Shortcut handling PR #581 - Wire-up keyboard navigation - Navigation system PR #695 - Focus hover styles - Visual feedback PR #834 - Trays keyboard nav - Tray navigation PR #962 - Combobox a11y - Complex components PR #994 - Fix up hotkey labels - Label accuracy PR #480 - Responsive card view - Card responsiveness PR #597 - Responsive trays - Tray adaptation PR #604 - Mobile columns - Column layout PR #739 - Mobile insets - Safe areas PR #740 - Mobile card improvements - Touch optimization PR #778 - Smaller action size on mobile - Size adaptation PR #881 - Mobile workflow styles - Workflow on mobile PR #1208 - Dynamic height for pins based on viewport - Viewport handling PR #310 - Collection notification settings - Preference foundation PR #1088 - Watchers - Watcher system PR #1099 - Watching polish - UI refinement PR #1228 - Fix watchers list caching - Cache issues PR #1231 - Fix watching card inconsistencies - State consistency PR #1239 - Higher fidelity watching/unwatching - Reliability PR #1432 - Subscribe to page changes via turbo streams - Real-time updates PR #1519 - Clean watchers when losing access - Data cleanup PR #554 - Create beta environment and move secrets - Rails credentials PR #584 - Introduce staging environment - Environment setup PR #647 - Don’t require encrypted credentials in test - Test isolation PR #863 - Dev env improvements for new accounts - Development DX PR #1911 - Email delivery via env vars - Env configuration PR #1976 - CSP gives env config precedence - Config override PR #207 - Drag bubble divider - Basic dragging PR #209 - Fix divider drag jankiness - Smooth interaction PR #607 - Drag and drop cards between stages - Card movement PR #1927 - Faster D&D with optimistic insertion - Performance