Latest Posts (14 found)
Rob Zolkos 2 days ago

So where can we use our Claude subscription then?

There’s been confusion about where we can actually use a Claude subscription. This comes after Anthropic took action to prevent third-party applications from spoofing the Claude Code harness to use Claude subscriptions. The information in this post is based on my understanding from reading various tweets, official GitHub repos and documentation (some of which may or may not be up to date). I will endeavour to keep it up to date as new information becomes available. I would love to see Anthropic themselves maintain an easily parsable page like this that shows what is and is not permitted with a Claude subscription. We've taken action to prevent third-party clients from spoofing the Claude Code agent harness to use consumer subscriptions. Consumer subscriptions and their benefits should only be used in the Anthropic experiences they support (Claude Code CLI, Claude Code web, and via sessionKey in the Agent SDK). Third-party apps can use the API. From what I can gather, consumer subscriptions work with official Anthropic tools, not third-party applications. If you want third-party integrations, you need the API. The consumer applications (desktop and mobile) are the most straightforward way to use your Claude subscription. Available at claude.com/download , these apps give you direct access to Claude for conversation, file uploads, and Projects. The official command-line interface for Claude Code is fully supported with Claude subscriptions. This is the tool Anthropic built and maintains specifically for developers who want to use Claude in their development workflow. You get the full power of Claude integrated into your terminal, with access to your entire codebase, the ability to execute commands, read and write files, and use all the specialized agents that come with Claude Code. The web version of Claude Code (accessible through your browser at claude.ai/code) provides the same capabilities as the CLI but through a browser interface. Upload your project files, or point it at a repository, and you can work with Claude on your codebase directly. Want to experiment with building custom agents? The Claude Agent SDK lets you develop and test specialized agents powered by your Claude subscription for personal development work. The SDK is available in both Python and TypeScript , with documentation here . This is for personal experiments and development. For production deployments of agents, use the API instead of your subscription. You can use your Claude subscription to run automated agents in GitHub Actions. The Claude Code Action lets you set up workflows that leverage Claude for code review, documentation generation, or automated testing analysis. Documentation is here . Any other uses of Claude would require the use of API keys. Your Claude subscription gives you: Let me know if you have any corrections. Claude desktop and mobile apps for general use Claude Code CLI for terminal-based development Claude Code on the web for browser-based work The ability to build custom agents through the official SDK (for personal development) Claude Code GitHub Action for CI/CD integration

1 views
Rob Zolkos 1 weeks ago

A Month Exploring Fizzy

In their book Getting Real , 37signals talk about Open Doors — the idea that you should give customers access to their data through RSS feeds and APIs. Let them get their information when they want it, how they want it. Open up and good things happen. Fizzy takes that seriously. When 37signals released Fizzy with its full git history available , they didn’t just open-source the code — they shipped a complete API and webhook system too. The doors were wide open baby! So I dove in — reading the source, building tools, and sharing what I found. Every time curiosity kicked in, there was a direct path from “I wonder if…” to something I could actually try and execute. This post is a catch-all for my very bubbly month of December. Fizzy Webhooks: What You Need to Know — I set up a local webhook receiver to capture and document every event type Fizzy sends. The post covers the payload structures, signature verification, and ideas for what you could build on top of the webhook system. The Making of Fizzy, Told by Git — I prompted Claude Code to analyze the entire git history and write a documentary about the development. Vanilla CSS is all you need — Diving into the no-build CSS architecture across Campfire, Writebook, and Fizzy. Fizzy Design Evolution: A Flipbook from Git — I went through each day of commits, got the application to a bootable state, seeded the database, and took a screenshot. Then I stitched those screenshots into a flipbook video with a soundtrack made from Fizzy’s own audio files. Fizzy’s Pull Requests: Who Built What and How — An analysis of who owned which domains in the Fizzy codebase. The post maps contributors to their expertise areas and curates learning paths through the PRs for topics like Turbo/Hotwire, caching, AI integration, multi-tenancy, and webhooks. The open API invited experimentation. I spotted gaps that would make integration easier for other developers, so I filled them: fizzy-api-client — Ruby client for the Fizzy API. fizzy-client-python — Python client for the Fizzy API. fizzy-cli — Command-line interface for the Fizzy API, built first in Ruby and then migrated to Go for portability. fizzy-skill — An AI agent skill for interacting with Fizzy. n8n-nodes-fizzy — An n8n community node that brings Fizzy into your automation workflows. Create cards, manage assignments, and react to real-time events through webhook triggers. Migration tools — I built these to make it easier to try Fizzy without starting from scratch. Migrating your existing issues and boards gives you an immediate sense of how it could work for you, without having to manually create test cards. You can see your real data running in Fizzy from day one, which I think makes it easier to evaluate and decide if its useful for you. I also contributed a few small fixes back to the main repository: Fizzy is released under the O’Saasy License , which is similar in spirit to MIT but includes a restriction on offering the software as a competing hosted or SaaS product. You can modify and self-host it, but you can’t repackage it and sell it as your own hosted service. I built O’Saasy Directory to make it easy to find applications released under this license. Beyond Fizzy, the directory includes other submitted projects where the source is available to read and modify. If you have built something under the O’Saasy License, visit the submission page to add yours. Having built the Fizzy CLI and fizzy-api-client Rubygem, I saw some fun opportunities to build little lab experiments to show how Fizzy could be integrated with - both to power up some functionality that isn’t there yet, but also creating boards in some interesting ways (eg Movie Quiz). I got the idea for this on a flight to Australia with no internet. Just a pad of paper and a pen. I should probably do that more often as a bunch of ideas for all sorts of products came out. CarbonationLabs is not a product per se. It’s an open source Rails application designed to be run locally where you can interact with the hosted or self-hosted versions of Fizzy. If anything I hope it inspires creation of little problem solving workflows for Fizzy that wouldn’t be built into the main product (the problem is too niche). The API and webhook system is really flexible and most of your bespoke problems could be solved with some creative thinking. Introducing Carbonation Labs - fun ways to add experiments to and extend Fizzy (repo link and demo videos below)🧵 I built carbonation.dev to bring together all the tools, libraries, and integrations that I and others in the community have created for Fizzy. It’s a directory covering API clients (Ruby, Python, JavaScript), CLI tools with packages for macOS, Arch Linux, Debian, Fedora, and Windows, integrations for Claude Code and other AI agents, n8n, Raycast, Telegram, and MCP servers, plus migration tools for GitHub, Linear, Asana, and Jira. If you’ve built something for Fizzy, I’d love to feature it. You can submit a pull request to add your tool to the directory. Building the Fizzy CLI pushed me into some new territory. I created an AUR package for Arch Linux users, set up a Homebrew tap for macOS, published my first Python package to PyPI, and made an n8n plugin — all firsts for me. While I already knew Go, rewriting the CLI in it was a fun exercise, and building TUIs for the setup and skill commands introduced me to terminal UI libraries I hadn’t used before. Gosh it was fun! If you want to get better at Rails, Fizzy is a great place to study real-world code. And in my view if you want to work at 37signals as a Rails programmer, digging into Fizzy — along with Campfire and Writebook — is a solid way to learn how they approach Rails architecture and design decisions. Submitting PRs is also a good way to contribute back while learning — just be respectful of the contribution policy . The review discussions give you a window into how to reason about problems, spot opportunities, and make trade-offs. This month pushed parts of my creative thinking that weren’t gone, but definitely weren’t being stressed. Like any muscle, use it or lose it. The direction of what to explore came from my own curiosity and a habit of poking around under the hood, and AI helped me move a lot faster once I knew where I wanted to go. Most of this information already exists somewhere — Google, Stack Overflow, documentation — but having AI right there alongside me as a partner was thrilling. All of this was made possible because a team left the doors open. No one asked me to step inside; I decided to invest the time and do the work to see what I could build, learn and share. I do this at work too—when I can—looking for opportunities I can shape, experiment with, and get genuinely excited about. Most importantly I had fun and I hope you enjoyed following along. linear2fizzy — Migrate Linear issues jira2fizzy — Migrate JIRA issues asana2fizzy — Migrate Asana tasks gh2fizzy — Migrate GitHub Issues prd2fizzy — Convert PRDs to Fizzy cards #2114 — Remove unused install.svg and its CSS class #2111 — Remove unpaired view-transition-name #2095 — Fix typo: minues → minutes #2094 — Fix duplicate word: use use → use #2093 — Add QrCodesController test #2088 — Fix view-transition-name typo in public card show

0 views
Rob Zolkos 1 months ago

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

0 views
Rob Zolkos 1 months ago

Fizzy Design Evolution: A Flipbook from Git

After writing about the making of Fizzy told through git commits , I wanted to see the design evolution with my own eyes. Reading about “Let’s try bubbles” and “Rename bubbles => cards” is one thing. Watching the interface transform over 18 months is another. So I got to work: I went through each day of commits in the Fizzy repository , got the application to a bootable state, seeded the database, and took a screenshot. Then I stitched those screenshots together into a flipbook-style video. Here’s the final result - I hope you enjoy it! Read on below for details about the process and the backing music. The Fizzy repository has 8,152 commits spanning from June 2024 to November 2025. I extracted the git SHA of the last commit from each day, excluding dependabot commits - a good proxy for the state of the app at the end of each work day. With that list I then copied the Fizzy source into dated folders and checked out the corresponding SHA for each. I then wrote and ran a sanity check script to ensure the right Ruby versions were installed, that worked, and that database migrations and seeds completed successfully. Once each folder was ready, the screenshot process began: I worked in batches of 10 checkpoints at a time to verify I was capturing the right page, since routes changed throughout the project’s history. Not every checkpoint was bootable. Schema migrations sometimes broke older seeds. Gems changed. Dependencies shifted—including private gems I didn’t have access to. Some checkpoints required manual fixes to get the application running. But persistence paid off - I captured all the screenshots to show the visual journey from “splats on a windshield” (the original playful blob metaphor) to the polished kanban board that shipped. As I watched the completed flipbook, it lacked any backing music—kinda boring! I remembered Jason Fried mentioning on the Fizzy livestream demo that if you hold Shift while dragging a card to another column, Fizzy plays a little sound—a different one per column. So I did what anyone making a flipbook out of git history screenshots in 2025 would do: I asked Claude to stitch those original sound files from the repo into a soundtrack. Because what else would you use to score a Fizzy flipbook besides Fizzy’s own sound files? Rob: Tell me about the audio files in this repo Claude: There are 30 MP3 audio files in public/audio/, organized by instrument: These appear to be musical note samples, likely used for notification sounds or some kind of audio feedback feature in the app. Rob: From these notes is it possible to make a tune? (not asking you to, just whether the notes themselves are condusive to any sort of melodic arrangement?) Claude: Yes, these notes are well-suited for creating melodies. Here's why: The notes available: C3, D4, E3, F#4, G3, B3 This is essentially a G major pentatonic scale (with some octave displacement): Why it works melodically: Limitations: But for short jingles, notification sounds, or simple tunes - absolutely workable. You could easily create something pleasant and recognizable with these. Rob: and with the variations in instruments I take it it would be possible to make it pretty interesting Claude: Exactly. With 5 distinct timbres (banjo, harpsichord, mandolin, piano, vibes), you could: Layer and contrast: Create call-and-response: Build dynamics: Evoke different moods: The instruments also share similar attack characteristics (plucked/struck, quick decay) so they'd blend well together without clashing sonically. Rob: well I want to make a background track for a demo video of this application. it is composed of screenshots of the application as it evolved over time. And then stitched into a flipbook with a 2 second gap between shots. Is this something you can work with and compose a tune with these files? Claude: That sounds like a fun project! Yes, I can work with this. To compose something appropriate, I need a few details: Once I know the duration and mood, I can sketch out a melodic sequence that evolves over the timeline - maybe starting simple and building as the app matures in the screenshots. Rob: a single combined audio file. about 2 minutes long. dramatic would be good but starting off slow tempo and then building into a fast crescendo. reminiscent of early computer demos Claude: Love it. Early computer demo scene vibes - think Amiga/C64 era, building from sparse arpeggios to a frenetic finale. Let me check if ffmpeg is available and explore the audio files a bit more... From there, Claude ensured ffmpeg was installed and got to work. We iterated a few times - “vibe music produced” - until it sounded right. A really fun way to bring some life to what would have otherwise been a silent flipbook. Watching the design evolve frame by frame reveals things the commit history can only hint at: Epicenter design. You can see epicenter design in action—a philosophy that’s been at the core of 37signals’ process for decades. The team started with the most essential elements (the grouping of bubbles/cards) and built outward from there. Navigation, chrome, and secondary features came later. The flipbook shows this progression clearly: core functionality first, polish second. The color exploration. Early versions experimented with bold, vibrant colors. The palette refined over time into something more subdued and professional while retaining the signature fun style 37signals applications are known for. Layout density. The interface started sparse and grew more information-dense as features accumulated, then simplified again in the later months. The bubble-to-card transition. You can see the playful, organic blob shapes gradually give way to more structured rectangular cards. The whimsy of the early windshield metaphor fades into the pragmatism of a task management tool. Typography and spacing. Subtle but constant. Every few frames you notice tighter line heights, adjusted padding, refined font weights. The polish accumulated through hundreds of small commits. Animation. You can’t see the subtle hover animations in a flipbook of static screenshots, but the team experimented with movement and texture throughout development. Here are a few examples: This kind of archaeology is only possible because 37signals kept the full git history intact. Most companies would have squashed or rebased this history into oblivion before open-sourcing. The fact that they didn’t is a gift to anyone curious about how software really gets made. Let me know if you try something similar with another codebase. The process is time-consuming but oddly satisfying. Boot the application Use Playwright to log in and navigate to the correct page Capture the screenshot banjo/ - 6 files (B3, C3, D4, E3, Fsharp4, G3) harpsichord/ - 6 files (same notes) mandolin/ - 6 files (same notes) piano/ - 6 files (same notes) vibes/ - 6 files (same notes) G, B, C, D, E, F# → all notes from G major, missing only A Pentatonic foundation - Pentatonic scales are inherently melodic and almost impossible to make sound "wrong." They're used across virtually all musical cultures. No dissonant intervals - There are no half-step clashes. Any combination of these notes will sound consonant together. Strong tonal center - G3 provides a natural "home" note, with B3 and D4 forming a G major triad. Range - Spanning from C3 to F#4 (about an octave and a half) gives enough range for interesting melodic contour. Only 6 notes, so extended melodies might feel repetitive The octave jumps (C3 vs D4, E3 vs F#4) could make smooth stepwise motion tricky No A note, which limits some common melodic patterns Use piano or vibes for a main melody line Add harpsichord for ornamentation or counterpoint Bring in banjo or mandolin for rhythmic texture Piano plays a phrase, vibes answer Or trade off between the plucked strings (banjo/mandolin) and the keyboard instruments Start sparse with a single instrument Layer in others for a fuller sound Drop back down for resolution Vibes alone = mellow, dreamy Harpsichord = baroque, playful Banjo = folksy, upbeat Piano = neutral, versatile Mandolin = bright, energetic How many screenshots/frames are there? (This tells me the total duration - frames × 2 seconds) What's the overall vibe you're going for? Upbeat and celebratory (look how far we've come!) Calm and reflective (a journey through time) Playful and quirky Professional/polished What format do you need the output in? A single combined audio file (I'd need to use ffmpeg or similar to stitch the samples) A script/sequence that describes what plays when (for you to assemble) Something else? Any preference on lead instrument? Piano tends to be the safest "neutral" choice, but if you have a vibe in mind, let me know.

0 views
Rob Zolkos 1 months ago

Vanilla CSS is all you need

Back in April 2024, Jason Zimdars from 37signals published a post about modern CSS patterns in Campfire . He explained how their team builds sophisticated web applications using nothing but vanilla CSS. No Sass. No PostCSS. No build tools. The post stuck with me. Over the past year and a half, 37signals has released two more products (Writebook and Fizzy) built on the same nobuild philosophy. I wanted to know if these patterns held up. Had they evolved? I cracked open the source code for Campfire, Writebook, and Fizzy and traced the evolution of their CSS architecture. What started as curiosity became genuine surprise. These are not just consistent patterns. They are improving patterns. Each release builds on the last, adopting progressively more modern CSS features while maintaining the same nobuild philosophy. These are not hobby projects. Campfire is a real-time chat application. Writebook is a publishing platform. Fizzy is a full-featured project management tool with kanban boards, drag-and-drop, and complex state management. Combined, they represent nearly 14,000 lines of CSS across 105 files. Not a single line touches a build tool. Let me be clear: there is nothing wrong with Tailwind . It is a fantastic tool that helps developers ship products faster. The utility-first approach is pragmatic, especially for teams that struggle with CSS architecture decisions. But somewhere along the way, utility-first became the only answer. CSS has evolved dramatically. The language that once required preprocessors for variables and nesting now has: 37signals looked at this landscape and made a bet: modern CSS is powerful enough. No build step required. Three products later, that bet is paying off. Open any of these three codebases and you find the same flat structure: That is it. No subdirectories. No partials. No complex import trees. One file per concept, named exactly what it does. Zero configuration. Zero build time. Zero waiting. I would love to see something like this ship with new Rails applications. A simple starting structure with , , , and already in place. I suspect many developers reach for Tailwind not because they prefer utility classes, but because vanilla CSS offers no starting point. No buckets. No conventions. Maybe CSS needs its own omakase. Jason’s original post explained OKLCH well. It is the perceptually uniform color space all three apps use. The short version: unlike RGB or HSL, OKLCH’s lightness value actually corresponds to perceived brightness. A 50% lightness blue looks as bright as a 50% lightness yellow. What is worth noting is how this foundation remains identical across all three apps: Dark mode becomes trivial: Every color that references these primitives automatically updates. No duplication. No separate dark theme file. One media query, and the entire application transforms. Fizzy takes this further with : One color in, four harmonious colors out. Change the card color via JavaScript ( ), and the entire card theme updates automatically. No class swapping. No style recalculation. Just CSS doing what CSS does best. Here is a pattern I did not expect: all three applications use units for horizontal spacing. Why characters? Because spacing should relate to content. A gap between words feels natural because it is literally the width of a character. As font size scales, spacing scales proportionally. This also makes their responsive breakpoints unexpectedly elegant: Instead of asking “is this a tablet?”, they are asking “is there room for 100 characters of content?” It is semantic. It is content-driven. It works. Let me address the elephant in the room. These applications absolutely use utility classes: The difference? These utilities are additive , not foundational. The core styling lives in semantic component classes. Utilities handle the exceptions: the one-off layout adjustment, the conditional visibility toggle. Compare to a typical Tailwind component: And the 37signals equivalent: Yes, it is more CSS. But consider what you gain: If there is one CSS feature that changes everything, it is . For decades, you needed JavaScript to style parents based on children. No more. Writebook uses it for a sidebar toggle with no JavaScript: Fizzy uses it for kanban column layouts: Campfire uses it for intelligent button styling: This is CSS doing what you used to need JavaScript for. State management. Conditional rendering. Parent selection. All declarative. All in stylesheets. What fascinated me most was watching the architecture evolve across releases. Campfire (first release) established the foundation: Writebook (second release) added modern capabilities: Fizzy (third release) went all-in on modern CSS: You can see a team learning, experimenting, and shipping progressively more sophisticated CSS with each product. By Fizzy, they are using features many developers do not even know exist. CSS Layers solve the specificity wars that have plagued CSS since the beginning. It does not matter what order your files load. It does not matter how many classes you chain. Layers determine the winner, period. One technique appears in all three applications that deserves special attention. Their loading spinners use no images, no SVGs, no JavaScript. Just CSS masks. Here is the actual implementation from Fizzy’s : The keyframes live in a separate file: Three dots, bouncing in sequence: The means it automatically inherits the text color. Works in any context, any theme, any color scheme. Zero additional assets. Pure CSS creativity. The default browser element renders as a yellow highlighter. It works, but it is not particularly elegant. Fizzy takes a different approach for search result highlighting: drawing a hand-drawn circle around matched terms. Here is the implementation from : The HTML structure is . The empty exists solely to provide two pseudo-elements ( and ) that draw the left and right halves of the circle. The technique uses asymmetric border-radius values to create an organic, hand-drawn appearance. The makes the circle semi-transparent against the background, switching to in dark mode for proper blending. Search results for: webhook No images. No SVGs. Just borders and border-radius creating the illusion of a hand-drawn circle. Fizzy and Writebook both animate HTML elements. This was notoriously difficult before. The secret is . Here is the actual implementation from Fizzy’s : The variable is defined globally as . Open Dialog This dialog animates in and out using pure CSS. The rule defines where the animation starts from when an element appears. Combined with , you can now transition between and . The modal smoothly scales and fades in. The backdrop fades independently. No JavaScript animation libraries. No manually toggling classes. The browser handles it. I am not suggesting you abandon your build tools tomorrow. But I am suggesting you reconsider your assumptions. You might not need Sass or PostCSS. Native CSS has variables, nesting, and . The features that needed polyfills are now baseline across browsers. You might not need Tailwind for every project. Especially if your team understands CSS well enough to build a small design system. While the industry sprints toward increasingly complex toolchains, 37signals is walking calmly in the other direction. Is this approach right for everyone? No. Large teams with varying CSS skill levels might benefit from Tailwind’s guardrails. But for many projects, their approach is a reminder that simpler can be better. Thanks to Jason Zimdars and the 37signals team for sharing their approach openly. All code examples in this post are taken from the Campfire, Writebook, and Fizzy source code. For Jason’s original deep-dive into Campfire’s CSS patterns, see Modern CSS Patterns and Techniques in Campfire . If you want to learn modern CSS, these three codebases are an exceptional classroom. Native custom properties (variables) Native nesting Container queries The selector (finally, a parent selector) CSS Layers for managing specificity for dynamic color manipulation , , for responsive sizing without media queries HTML stays readable. tells you what something is, not how it looks. Changes cascade. Update once, every button updates. Variants compose. Add without redefining every property. Media queries live with components. Dark mode, hover states, and responsive behavior are co-located with the component they affect. OKLCH colors Custom properties for everything Character-based spacing Flat file organization View Transitions API for smooth page changes Container queries for component-level responsiveness for entrance animations CSS Layers ( ) for managing specificity for dynamic color derivation Complex chains replacing JavaScript state

0 views
Rob Zolkos 1 months ago

The Making of Fizzy, Told by Git

Today Fizzy was released and the entire source code of its development history is open for anyone to see . DHH announced on X that the full git history is available - a rare opportunity to peek behind the curtain of how a 37signals product comes together. I cloned down the repository and prompted Claude Code: “Can you go through the entire git history and write a documentary about the development of this application. What date the first commit was. Any major tweaks, changes and decisions and experiments. You can take multiple passes and use sub-agents to build up a picture. Make sure to cite commits for any interesting things. If there is anything dramatic then make sure to see if you can figure out decision making. Summarize at the end but the story should go into STORY.md” It responded with: “This is a fascinating task! Let me create a comprehensive investigation plan and use multiple agents to build up a complete picture of this project’s history.” Here is the story of Fizzy - as interpreted by Claude - from the trail of git commits. Enjoy! A chronicle of 18 months of development at Basecamp, told through 8,152 commits. At 1:19 PM on a summer Friday, Kevin McConnell typed the words that would begin an 18-month journey: Within hours, the foundation was laid. The team moved with practiced efficiency: By end of day, the skeleton of a Rails application stood ready. But what would it become? One month after inception, Jason Zimdars introduced the application’s first real identity: A “Splat” — the name evokes something chaotic, impactful, unexpected. Like a bug hitting your windshield on a summer drive. The original data model was simple: The next day brought the visual metaphor that would define the early application: The windshield was the canvas. Splats appeared on it like bugs on glass — colorful, slightly chaotic, each one a piece of information demanding attention. The commits reveal urgency. Something important was coming: The all-hands demo. Approximately one month after project inception, Fizzy (then still called “Splat”) was shown to the entire company. The pressure to polish was evident in the commit messages. Seven days after the windshield metaphor was established, Jason Zimdars typed four words that would reshape the application’s identity: The chaotic “splat” gave way to something gentler — bubbles floating on a windshield , like soap suds catching light. The animation changed from aggressive splattering to gentle floating: Perfect circles gave way to hand-drawn blob shapes. The team was discovering what their product was through the act of building it. A new interaction pattern emerged: When users “boosted” a bubble, it would puff up and float away — like champagne fizz rising. The animation: The metaphor was crystallizing. Bubbles. Fizzing. Effervescence. The name would come soon. In a single day, the application found its final name through two commits: 42 files changed. The model, controllers, views, tests — everything touched. Hours later: Fizzy. The name captured everything: the bubbles, the effervescence, the playful energy of the interface. Visual design had driven product naming — the team discovered what they were building through the act of building it. The flat list of bubbles needed structure: But “Projects” didn’t feel right. Eight days later: Then “Bucket” became “Collection.” Eventually, “Collection” would become “Board.” The terminology dance — Projects → Buckets → Collections → Boards — reveals a team searching for the right mental model. They ultimately landed on the familiar “Board” metaphor, aligning with tools like Trello and Linear. David Heinemeier Hansson, creator of Ruby on Rails and co-founder of Basecamp, made his first contribution with characteristic pragmatism: He deleted an unused image file. It was a statement of intent. Within two days, DHH’s fingerprints were everywhere: He upgraded the entire application to Rails 8 release candidate and systematically added HTTP caching throughout. DHH’s most distinctive contribution was his crusade against what he called “anemic” code — thin wrappers that explain nothing and add needless indirection. He used this term 15 times in commit messages: Philosophy: Code should either add explanatory value OR hide implementation complexity. Thin wrappers that do neither are “anemic” and should be eliminated. Then came April 2025. DHH made 323 commits in a single month — 55% of his total contributions compressed into 30 days. This was a surgical strike. He: His commit messages tell the story: In DHH’s philosophy: deletion is a feature, not a bug. After 10 months as “Bubbles,” another transformation: 333 files changed. “Pop” (completing a bubble) became “Closure” (closing a card). The playful metaphor gave way to task management vocabulary. The final architectural piece: Fizzy had become a kanban board . Cards lived in columns. Columns could be customized, colored, reordered. The application had evolved from “bugs on a windshield” to a sophisticated project management tool. Collections became Boards. The transformation was complete: Original (July 2024): Final (November 2025): A Claude-powered AI assistant that could answer questions about project content. Born, restricted to staff, then removed entirely. Perhaps replaced by the more ambitious MCP (Model Context Protocol) integration — making Fizzy AI-native at the protocol level rather than bolting on a chatbot. Emoji reactions for cards and comments. Added. Removed. Then added again. The git history shows healthy debate — not everything that ships stays shipped, and not everything removed stays gone. Saved custom views were replaced by ephemeral quick filters. Complexity gave way to simplicity. Predefined workflows with stages were removed in favor of ad-hoc column organization. Users would create their own structure. The MCP (Model Context Protocol) branch represents cutting-edge AI integration — allowing Claude and other AI assistants to interact with Fizzy programmatically. An manifest advertises Fizzy’s capabilities to AI clients. Status: Removed from main, but the infrastructure remains fascinating. This is one of the earliest explorations of making traditional web applications AI-native. Multiple parallel branches exploring different approaches to mobile column navigation. Scroll snapping. Contained scrolling. Swipeable columns. The problem remains unsolved — there’s no “one true way” for mobile kanban navigation. Making Fizzy work with SQLite in addition to MySQL. Simpler local development. Better portability. The search index was even sharded into 16 tables ( through ) for scale. The proprietary SAAS features were extracted into a separate gem. What remained was a clean, open-source Rails application. After 18 months of development, 8,152 commits, and countless pivots, Fizzy became open source. Jason Zimdars (2,217 commits) — The visual architect. From “Let’s try bubbles” to pixel-perfect polish. Jorge Manrubia (2,053 commits) — The engineering backbone. Consistent, prolific, essential. Andy Smith (1,007 commits) — Front-end craftsmanship and UI refinement. Mike Dalessio (875 commits) — Infrastructure, performance, the recent dashboard work. David Heinemeier Hansson (586 commits) — The architectural enforcer. Rails modernization and the war on anemic code. Kevin McConnell (351 commits) — Started it all with “New Rails app.” Jose Farias (341 commits) — Feature development and testing. Stanko K.R. (239 + 54 commits) — Security hardening and webhook restrictions. Jeffrey Hardy (100 commits) — Early infrastructure and modernization. Jason Fried (7 commits) — The occasional “Small copy adjustment” from the CEO. July 2024 (v0.1): September 2024 (v0.2): November 2025 (v1.0): The story of Fizzy is the story of discovery through building . The team didn’t know they were building a kanban board when they started with “splats on a windshield.” They found out through iteration. Key lessons: Names matter, but they can change. Splat → Bubble → Card. Project → Bucket → Collection → Board. The right name emerges through use. Deletion is a feature. Boosts, Fizzy Ask, custom views, workflows — removing the wrong features is as important as adding the right ones. Architecture evolves. The final column-based kanban system looks nothing like the original flat list of splats. DHH’s philosophy: Remove anemic code. Keep transactions short. Use the latest Rails. Delete more than you add. Design drives naming. “Fizzy” emerged from the visual metaphor of bubbles puffing up and floating away — the design informed the brand. Open source takes extraction. 18 months of SAAS development needed careful separation before the core could be shared. The git history of Fizzy is a masterclass in iterative product development. 8,152 commits. 25+ contributors. 18 months. One application that discovered its identity through the act of creation. “Let’s try bubbles.” — Jason Zimdars, July 31, 2024 Documentary compiled December 2, 2025 Based on analysis of the Fizzy git repository First Commit: June 21, 2024 Total Commits: 8,152 Contributors: 25+ Lines of Code Changed: Hundreds of thousands Name Changes: 4 (Splat → Bubble → Card; Project → Bucket → Collection → Board) Features Removed: At least 4 major ones DHH Commits in April 2025 Alone: 323 1:23 PM — Gemfile updated ( ) 3:47 PM — Rubocop configured ( ) 4:07 PM — Minimal authentication flow ( ) 4:29 PM — CSS reset and base styles ( ) 4:46 PM — Brakeman security scanning added ( ) Removed the entire Boosts feature ( ) — 299 lines across 27 files, gone Eliminated activity scoring ( , , ) Extracted RESTful controllers from overloaded ones ( , ) Enforced transaction discipline ( — “No long transactions!”) Splats on a Windshield Cards → Columns → Boards → Accounts Jason Zimdars (2,217 commits) — The visual architect. From “Let’s try bubbles” to pixel-perfect polish. Jorge Manrubia (2,053 commits) — The engineering backbone. Consistent, prolific, essential. Andy Smith (1,007 commits) — Front-end craftsmanship and UI refinement. Mike Dalessio (875 commits) — Infrastructure, performance, the recent dashboard work. David Heinemeier Hansson (586 commits) — The architectural enforcer. Rails modernization and the war on anemic code. Kevin McConnell (351 commits) — Started it all with “New Rails app.” Jose Farias (341 commits) — Feature development and testing. Stanko K.R. (239 + 54 commits) — Security hardening and webhook restrictions. Jeffrey Hardy (100 commits) — Early infrastructure and modernization. Jason Fried (7 commits) — The occasional “Small copy adjustment” from the CEO. July 24, 2024: “Handful of tweaks before all-hands” — Demo day pressure July 31, 2024: “Let’s try bubbles” — The visual pivot September 4, 2024: “Splat -> Fizzy” — Finding the name April 2025: DHH’s 323-commit refactoring blitz October 2025: “Remove Fizzy Ask” — The AI feature that didn’t survive November 28, 2025: “Initial README and LICENSE” — Going public Rails 8.x — Always on the latest, sometimes ahead of stable Hotwire (Turbo + Stimulus) — No heavy JavaScript framework Solid Queue & Solid Cache — Rails-native background jobs and caching SQLite + MySQL support — Database flexibility Kamal deployment — Modern container orchestration UUID primary keys — Using UUIDv7 for time-ordering Multi-tenancy — Account-based data isolation Names matter, but they can change. Splat → Bubble → Card. Project → Bucket → Collection → Board. The right name emerges through use. Deletion is a feature. Boosts, Fizzy Ask, custom views, workflows — removing the wrong features is as important as adding the right ones. Architecture evolves. The final column-based kanban system looks nothing like the original flat list of splats. DHH’s philosophy: Remove anemic code. Keep transactions short. Use the latest Rails. Delete more than you add. Design drives naming. “Fizzy” emerged from the visual metaphor of bubbles puffing up and floating away — the design informed the brand. Open source takes extraction. 18 months of SAAS development needed careful separation before the core could be shared.

0 views
Rob Zolkos 1 months ago

Fizzy Webhooks: What You Need to Know

Fizzy is a new issue tracker ( source available ) from 37signals with a refreshingly clean UI. Beyond looking good, it ships with a solid webhook system for integrating with external services. For most teams, webhooks are the bridge between the issues you track and the tools you already rely on. They let you push events into chat, incident tools, reporting pipelines, and anything else that speaks HTTP. If you are evaluating Fizzy or planning an integration, understanding what these webhooks can do will save you time. I also put together a short PDF with the full payload structure and example code, which I link at the end of this post if you want to go deeper. Here are a few ideas for things you could build on top of Fizzy’s events: If you want to go deeper, you can also build more opinionated tools that surface insights and notify people who never log in to Fizzy: Here is how to set it up. Step 1. Visit a board and click the Webhook icon in the top right. Step 2. Give the webhook a name and the payload URL and select the events you want to be alerted to. Step 3. Once the webhook saves you will see a summary of how it is setup and most importantly the webhook secret which you will need for your handler for securing the webhook. There is also a handy event log showing you when an event was delivered. Since I like to tinker with these sorts of things, I built a small webhook receiver to capture and document the payload structures. Fizzy sends HTTP POST requests to your configured webhook URL when events occur. Each request includes an header containing an HMAC-SHA256 signature of the request body. The verification process is straightforward: Fizzy covers the essential card lifecycle events: The approach was straightforward: I wrote a small Ruby script using WEBrick to act as a webhook receiver. The script listens for incoming POST requests, verifies the HMAC-SHA256 signature (using the webhook secret Fizzy provides when you configure webhooks), and saves each event as a separate JSON file with a timestamp and action name. This made it easy to review and compare the different event types later. To expose my local server to the internet, I used ngrok to create a temporary public URL pointing to port 4002. I then configured Fizzy’s webhook settings with this ngrok URL and selected the event types I wanted to capture. With everything set up, I went through Fizzy’s UI and manually triggered each available event: creating cards, adding comments, assigning and unassigning users, moving cards between columns and boards, marking cards as done, reopening them, postponing cards to “Not Now”, and sending cards back to triage. Each action fired a webhook that my script captured and logged. In total, I captured 13 webhook deliveries covering 10 different action types. The only event I could not capture was “Card moved to Not Now due to inactivity” — Fizzy triggers this automatically after a period of card inactivity, so it was not practical to reproduce during this test. Card body content is not included. The card object in webhook payloads only contains the , not the full description or body content. Comments include both and versions, but cards do not. Since Fizzy doesn’t have a public API ( DHH is working on it ), you can’t fetch the full card content programmatically - you’ll need to use the field to view the card in the browser. Column data is only present when relevant. The object only appears on , , and events - the events where a card actually moves to a specific column. IDs are strings, not integers. All identifiers in the payload are strings like , not numeric IDs. I created a short webhook documentation based on this research: FIZZY_WEBHOOKS.pdf It includes the full payload structure, all event types with examples, and code samples for signature verification in both Ruby and JavaScript. Hopefully this helps you get up and running with Fizzy’s webhooks. Let me know if you discover additional events or edge cases. Since the source code is available, you can also submit PRs to fix or enhance aspects of the webhook system if you find something missing or want to contribute improvements. A team metrics dashboard that tracks how long cards take to move from to and which assignees or boards close issues the fastest. Personal Slack or Teams digests that send each person a daily summary of cards they created, were assigned, or closed based on , , , and events. A churn detector that flags cards that bounce between columns or get sent back to triage repeatedly using , , and . A cross-board incident view that watches to keep a separate dashboard of cards moving into your incident or escalation boards. A comment activity stream that ships events into a search index or knowledge base so you can search discussions across boards. Stakeholder status reports that email non-technical stakeholders a weekly summary of key cards: what was created, closed, postponed, or sent back to triage on their projects. You can group by label, board, or assignee and generate charts or narrative summaries from , , , and events. Capacity and load alerts that watch for people who are getting overloaded. For example, you could send a notification to a manager when someone is assigned more than N open cards, or when cards assigned to them sit in the same column for too long without a or event. SLA and escalation notifications that integrate with PagerDuty or similar tools. When certain cards (for example, labeled “Incident” or on a specific board) are not closed within an agreed time window, you can trigger an alert or automatically move the card to an escalation board using , , and . Customer-facing status updates that keep clients in the loop without giving them direct access to Fizzy. You could generate per-customer email updates or a small status page based on events for cards tagged with that customer’s name, combining , , and to show progress and recent discussion. Meeting prep packs that assemble the last week’s events for a given board into a concise agenda for standups or planning meetings. You can collate newly created cards, reopened work, and high-churn items from , , , and , then email the summary to attendees before the meeting. - new card created - card moved to a column / - assignment changes - card moved to Done - card reopened from Done - card moved to Not Now - card moved back to Maybe? - card moved to different board - comment added to a card

0 views
Rob Zolkos 1 months ago

A Mermaid Validation Skill for Claude Code

AI coding agents generate significantly more markdown documentation than we used to write manually. This creates opportunities to explain concepts visually with mermaid diagrams - flowcharts, sequence diagrams, and other visualizations defined in text. When Claude generates these diagrams, the syntax can be invalid even though the code looks correct. Claude Code skills provide a way to teach Claude domain-specific workflows - in this case, validating diagrams before marking the work complete. Mermaid diagrams are text-based and version-controllable, which makes them useful for documentation. The syntax can be finicky: A diagram might look correct in markdown but fail to render. Without validation, you discover this only after the work is complete. Skills in Claude Code are markdown files that provide instructions for specific tasks. They can be invoked manually or triggered automatically when Claude detects a relevant situation. Here is the mermaid validation skill: The skill uses (mermaid CLI) for validation. Install it globally: Create the skill file at: Or in your project at: When Claude creates or edits a markdown file containing mermaid diagrams: Invalid diagrams have no value. Rather than leaving validation to the user, Claude verifies its own work. Manual validation requires remembering to ask for it every time. Skills make validation automatic and consistent. Every diagram gets checked, and errors are fixed before you see them. The pattern applies beyond mermaid diagrams. Any output that can be validated by a tool is a candidate for a validation skill. If a tool can identify errors, Claude can fix them before marking the task complete. Let me know if you have questions about setting this up. Missing arrows or incorrect arrow types Unbalanced brackets Invalid node names Typos in keywords The skill is automatically invoked Validation runs via Errors are fixed and re-validated Success is only reported when diagrams render correctly

0 views
Rob Zolkos 4 months ago

First Impressions of Sonic (the stealth xAi Grok coding model?)

Update (August 26, 2025) : Sonic has been confirmed as the new Grok Code model and is now officially available today! What was once a “stealth” model is now xAi’s publicly released coding assistant. A new stealth model called Sonic has quietly appeared in places like Opencode and Cursor, and it’s rumored to be xAi’s Grok coding model. I spent a full day working with it inside Opencode, replacing my usual Claude Sonnet 4 workflow — and came away impressed. The short version? It feels like Sonnet, but turbocharged. Sonic showed up in Opencode’s model selector — no special invite required. Selecting it as the active model made it fully functional, with no setup beyond choosing it in the dropdown. During this stealth period it’s free to use and offers a massive 256,000 token context window . I didn’t encounter any rate limits or throttling all day. Instead of a contrived “hello world,” I dropped Sonic into my real workflow. Where I’d usually use Sonnet 4, I asked Sonic to help me build a new Training Center CRUD feature in a Ruby on Rails application. It generated the models, migrations, controllers, and views exactly as expected — and most importantly, it did it fast . Text streamed out so quickly it was hard to keep up. Compared to Claude Code, I’d estimate 3x faster tokens per second , consistently. That was my wow moment: speed without sacrificing accuracy . It nailed the Rails conventions and felt like a drop-in Sonnet replacement. My go-to evaluation prompt for coding models is: “Tell me about this codebase and what the most complex parts are.” On my legacy Rails codebase, Sonic immediately identified the correct overloaded model as the main complexity hotspot, and even suggested ways to refactor it. The result was near identical in quality to what I’d get from Sonnet or even Opus — but again, the response flew back near-instantly. For day-to-day comprehension and reasoning, Sonic seems to match the best while dramatically cutting wait time. Bottom line: if you’re used to Sonnet, Sonic feels like the same experience at high speed. Right now Sonic is free to use in Opencode during its stealth rollout. No public pricing details are available yet, but if it undercuts Sonnet, it could be a winner. I haven’t seen any rate limits or regional restrictions. Cursor users also report seeing Sonic available as a selectable model. After a full day of work, I would — and did — ship production code with Sonic. It handled CRUD features, comprehension of a large legacy codebase, and various refactors without issue. The real differentiator is speed . If Sonnet is your baseline, Sonic offers the same reasoning ability but with response times that feel instantaneous. Unless something changes in pricing or reliability, this could easily become my daily driver. Reasoning & Accuracy : Nearly identical to Sonnet. No hallucinations, no weird Rails missteps. Personality : Very similar, though Sonic felt a bit more positive and agreeable to suggestions. Speed : The standout. At least 3x faster than Claude Code/Sonnet, with the speed holding steady across short and long generations. Where Sonnet Might Still Win : Hard to say yet — I didn’t hit any reasoning failures, but Sonnet have a longer track record of reliability under heavy workloads. Access : Select “Sonic” in Opencode’s model selector (no invite required during stealth) Pricing : Free during stealth; official pricing TBD Best for : Day-to-day coding tasks, CRUD features, codebase comprehension, Rails development Avoid for : Nothing obvious yet — but keep human oversight on critical code API docs : Not yet public Hot take : As I posted on X, this feels like my Sonnet replacement should I be forced to not have Sonnet . @robzolkos

0 views
Rob Zolkos 5 months ago

Two Days. Two Models. One Surprise: Claude Code Under Limits

The upcoming weekly usage limits announced by Anthropic for their Claude Code Max plans could put a dent in the workflows of many developers - especially those who’ve grown dependent on Opus-level output. I’ve been using Claude Code for the last couple of months, though nowhere near the levels I’ve seen from the top 5% users (some of whom rack up thousands of dollars in usage per day). I don’t run multiple jobs concurrently (though I’ve experimented), and I don’t run it on Github itself. I don’t use git worktrees (as Anthropic recommends ). I just focus on one task at a time and stay available to guide and assist my AI agent throughout the day. On Friday, I decided to spend the full day using Opus exclusively across my usual two or three work projects. Nothing unusual - a typical 8-hour day, bouncing between tasks. At the end of the day, I measured my token usage using the excellent utility and this calculated what it would have cost via the API. Then today (Monday), I repeated the experiment - this time using Sonnet exclusively . Different tasks of course, but the same projects, similar complexity, and the same eight-hour block. Again I recorded the token usage. Here’s what I found: Sonnet held up well for all of my coding tasks. I even used it for some light planning work and it got the job done (not as well as Opus would have but still very very good). Anthropic’s new limits suggest we’ll get 240-480 hours/week of Sonnet, and 24-40 hours/week of Opus. Considering a full-time work week is 40 hours, and there are a total of 168 total hours in a week, I think the following setup might actually be sustainable for most developers: I highly recommend you be explicit about which model you want to use for your custom slash commands and sub-agents . For slash commands there is a model attribute you can put in the command front matter . Release 1.0.63 also allows setting the model in sub-agents. I would love to see more transparency in the Claude Code UI of where we sit in real-time against our session and weekly limits. I think if developers saw this data they would control their usage to suit. We shouldn’t need 3rd party libraries to track and report this information. Based on this pattern, I don’t think I’ll hit the new weekly limits. But we’ll see - I’ll report back in September. And of course there is nothing stopping you from trialing other providers and models and even other agentic coding tools and really diving deep into using the best model for the job. Token usage was comparable. Sonnet’s cost was significantly lower (no surprise) And the quality? Honestly, surprisingly good. Sonnet for hands-on coding tasks Sonnet + Github for code review and analysis Opus for high-level planning, design, or complex architectural thinking

0 views
Rob Zolkos 5 months ago

Claude Code’s Feedback Survey

It seems this weekend a bunch of people have reported seeing Claude Code ask them for feedback on how well it is doing in their current session. It looks like this: And everyone finds it annoying. I feel things like this are akin to advertising. For a paid product, feedback surveys like this should be opt in. Ask me at the start of the session if I’m ok in providing feedback. Give me the parameters of the feedback and let me opt in. Don’t pester me when I’m doing work. I went digging in the code to see if maybe there is an undocumented setting I could slam into to hide this annoyance. What I found instead is an environment variable that switched it on more! will show that sucker lots! These are the conditions that will show the survey: Asking for feedback is totally ok. But don’t interrupt my work session to do it. I hope this goes away or there is a setting added to opt out completely. A minimum time before first feedback (600seconds / 10 minutes) A minimum time between feedback requests (1800 seconds / 30 minutes) A minimum number of user turns before showing feedback Some probability settings Some model restrictions (only shows for certain models) - I’ve only had it come up with Opus.

0 views
Rob Zolkos 5 months ago

AI Coding Assistants: The $200 Monthly Investment That Pays for Itself

Lately the price of AI coding assistants has been climbing. Claude Code’s max plan runs $200 per month. Cursor’s Ultra tier is another $200. Even GitHub Copilot has crept up to $39/month. It’s easy to dismiss these as too expensive and move on. But let’s do the math. A mid-level developer in the US typically costs their employer around $100 per hour when you factor in salary, benefits, and overhead. At that rate, an AI coding assistant needs to save just 2 hours per month to pay for itself. Two hours. That’s one debugging session cut short. One feature implemented faster. One refactoring completed smoothly instead of stretching into the evening. I’ve been using these tools for the past year, and I can confidently say they save me 2 hours in a typical day , not month. Here’s the catch: how do you prove the value before you have the subscription? Your boss wants evidence, but you need the tool to generate that evidence. This is a classic bootstrapping problem, but there are ways around it: Most AI coding assistants offer free trials or limited lower cost tiers. Use them strategically: Propose a 3-month pilot with clear success metrics: Yes, it’s an investment, but spending $200 of your own money for one month to demonstrate concrete value can be worth it. Track everything meticulously and present hard data. “We already pay for developers, why should we pay for their tools too?” You wouldn’t expect a carpenter to bring their own power tools. Modern development requires modern tooling. This is infrastructure, not a perk. “It’s just autocomplete, how much time can it really save?” This dramatically undersells what modern AI assistants do: “What if the AI writes bad code?” AI assistants don’t replace code review or testing. They accelerate the development cycle. Bad code gets caught the same way it always has – but you’ll have more time to write good code because you’re not bogged down in boilerplate. Here’s what many managers miss: your competitors are already using these tools. While you’re debating the cost, they’re shipping features faster, fixing bugs quicker, and keeping their developers happier. Developer productivity isn’t just about speed – it’s about: Burned out developers cost far more than $200/month in turnover, recruitment, and lost productivity. In 2025, asking whether to pay for AI coding assistants is like asking whether to pay for high-speed internet in 2010. It’s not a luxury – it’s table stakes for competitive software development. The question isn’t whether to adopt these tools, but how quickly you can integrate them effectively. Every month you wait is a month your competitors gain ground. When presenting to management, focus on: The $200 monthly cost? That’s not an expense. It’s the cheapest productivity multiplier you can buy. In a world where developer time is the scarcest resource, tools that amplify that time aren’t optional – they’re essential. Time yourself on similar tasks with and without the assistant Document specific examples where AI saved time Track error rates and bug fixes Reduced time to complete user stories Fewer bugs making it to production Increased test coverage Developer satisfaction scores Generate entire test suites Refactor legacy code Debug complex issues Write documentation Translate between programming languages Explain unfamiliar codebases Reduced cognitive load Less context switching Fewer repetitive tasks More time for creative problem-solving Concrete ROI : 2 hours saved = break even, everything else is profit Risk mitigation : Faster bug fixes, better code quality, happier developers Competitive advantage : Ship faster, iterate quicker, outpace competitors Talent retention : Developers want modern tools; denying them risks turnover

0 views
Rob Zolkos 5 months ago

Context7: The Missing Link for AI-Powered Coding

If you’ve spent any time working with AI code assistants like Cursor or Claude, you’ve probably encountered this frustrating scenario: you ask about a specific library or framework, and the AI confidently provides outdated information or hallucinates methods that don’t exist. This happens because most LLMs are trained on data that’s months or even years old. Enter Context7 – a clever solution to a problem that not many developers know about, but everyone experiences. What is Context7? Context7 is a documentation platform specifically designed for Large Language Models and AI code editors. It acts as a bridge between your AI coding assistant and up-to-date, version-specific documentation. Instead of relying on an LLM’s training data (which might be outdated), Context7 pulls real-time documentation directly from the source. This means when you’re working with a specific version of a library, your AI assistant gets accurate, current information. The Problem It Solves AI coding assistants face several documentation challenges: I’ve personally wasted hours debugging code that looked correct but used deprecated methods or non-existent parameters. Context7 aims to eliminate this friction. Key Benefits 1. Real-Time Documentation Context7 fetches documentation on-demand, ensuring you’re always working with the latest information. No more wondering if that method still exists in v3.0. 2. Version-Specific Accuracy Working with React 17 but your AI keeps suggesting React 18 features? Context7 provides version-aware documentation, matching your actual project dependencies. 3. Working Code Examples Instead of theoretical or hallucinated examples, you get real code snippets from the source documentation and repositories. MCP Integration One of Context7’s most interesting features is its integration with Model Context Protocol (MCP) servers. MCP is an emerging standard for providing structured context to AI models, and Context7 leverages this to deliver documentation more efficiently. Through their MCP Server on GitHub , you can: This integration means you’re not manually copying and pasting documentation – the relevant context flows seamlessly into your AI assistant. The Future of AI-Assisted Development Context7 represents an important evolution in AI-powered development tools. Rather than trying to solve the impossible problem of keeping LLMs perpetually up-to-date, it provides a practical middleware solution. As more developers discover tools like Context7, we’ll likely see: Context7 fills a crucial gap in the AI development ecosystem. While it might not be widely known yet, it addresses a universal pain point that every developer using AI tools has experienced. The next time you’re working with an AI coding assistant and need accurate, up-to-date documentation, remember that there’s a better way than hoping your LLM’s training data is recent enough. Context7 ensures your AI knows exactly what it’s talking about – because it’s checking the source in real-time. Outdated Training Data : Most LLMs are trained on data that’s at least several months old, missing recent API changes and new features Hallucinated Examples : AIs sometimes generate plausible-sounding but incorrect code examples Version Mismatches : Generic documentation doesn’t account for the specific version you’re using Context Overload : Pasting entire documentation files into your prompt wastes tokens and confuses the model Connect Context7 directly to Claude Code or other MCP-compatible tools Automatically inject relevant documentation based on your current context Maintain a clean separation between your code and documentation sources Better accuracy in AI-generated code Less time wasted on debugging hallucinated solutions More confidence in using AI assistants for production code A push toward standardized context protocols like MCP

0 views
Rob Zolkos 6 months ago

Screen Recording in Omarchy

As a Mac switcher to Omarchy I needed an easy way of doing quick screen recordings without firing up OBS Studio (which may or may not have an easy way to do screen recordings - I just haven’t learnt it yet). I was used to CleanShotX on the Mac which made it super simple to do quick demo screencasts (which I do often ) This guide will walk you through creating a screen recording system with a flashing red indicator in your waybar, automatic video compression, and seamless clipboard integration. What We’re Building Dependencies Before we start, install these packages on Arch: The Screen Recording Script Make it executable: Script Highlights Waybar Configuration Add this custom module to your waybar config ( ): How the Waybar Module Works Waybar Styling Add this to your waybar CSS ( ): This creates a red dot that blinks on and off every second, making it impossible to miss when you’re recording. Hyprland Keybinding Add this to your Hyprland config: Now you can: Troubleshooting If recording won’t start: If the red dot doesn’t appear: This setup gives you a simple screen recording workflow in Omarchy with visual feedback and automatic compression. The flashing red dot ensures you never forget you’re recording, and the hot-key operation makes it effortless to capture anything on your screen. Visual feedback : A flashing red dot in waybar while recording Region selection : Use slurp to select exactly what to record Audio support : Record with microphone audio or silent mode Smart compression : Automatically compress recordings with H.264 Clipboard integration : Video path copied to clipboard when done One-key operation : Start/stop recording with a single keybind - Wayland screen recorder - Region selection tool - Clipboard utilities for Wayland - For video compression - Status bar (you probably already have this) Toggle behavior : If already recording, stops the recording. Otherwise, starts a new one. Robust process handling : Tries graceful shutdown first, then force kills if needed (prevents stuck recordings). Automatic compression : Uses H.264 codec for universal browser compatibility while still reducing file size significantly. Smart notifications : Shows compression progress and file size reduction. Waybar integration : Sends signals to update the recording indicator. : Checks if is running every second Shows when recording, nothing when not : Clicking the dot runs the script (stops recording) : Updates every second for responsive feedback : Start recording with audio : Start recording without audio Press again to stop recording Press your keybind → Slurp opens for region selection Draw a rectangle → Recording starts, red dot appears in waybar Press keybind again → Recording stops, video is compressed Notification shows compression → “Size: 7.7MB → 2.3MB” File manager opens → Video path is in your clipboard High DPI screens : The script handles any resolution, but high DPI screens create larger files Disable compression : Use flag if you need the original quality Audio sources : The script auto-detects your default microphone Quick sharing : The video path is automatically copied to clipboard Check if a previous recording is stuck: Ensure all dependencies are installed Check waybar logs: Restart waybar: Check your waybar config syntax with

0 views