Building “Captain Cool”

Building “Captain Cool”: A Multi-Agent IPL Strategist Powered by Google Gemini in 3 Hours

Cricket is a captain’s game. But what if the captain was an autonomous, multi-agent AI “Brain Room” analyzing live weather, pitch soil, and historical matchups in real-time?

This weekend, I participated in the Agentic Premier League (APL) Hackathon organized by the incredible team at #gdgcloudpune. The challenge? Build an agentic AI system that acts as a virtual IPL captain—making tactical decisions under pressure exactly like Dhoni, Rohit, or Hardik. And the golden rule: It had to be built entirely on the Google Gemini ecosystem within a grueling 3-hour window.

Here is a deep dive into how I built Captain Cool 🏆.

🌟 The Problem: Moving Beyond the “Chatbot”

When building an AI for sports strategy, a generic LLM wrapper won’t cut it. If you ask a standard chatbot, “Who should bowl the next over?”, it will give you generic advice.

Real cricket requires context: Is dew setting in? Is the wind blowing toward the short boundary? Is it a red soil or black soil pitch?

Captain Cool solves this by moving away from a single prompt and utilizing an adversarial multi-agent architecture. You input the current match state, and the system replies with:

  1. 🎯 The Next Decision (e.g., bowling changes, field setups, Impact Player usage).
  2. 🧠 The Reasoning (explained in authentic commentator language).
  3. ⚔️ The Internal Debate (a raw, unedited back-and-forth between specialized agents).

🏗️ Architecture: Inside the “Brain Room”

To achieve this, I used Google’s Agent Development Kit (ADK) and the @google/genai SDK to create three distinct, named agents powered by gemini-2.5-pro. They don’t just generate text; they debate.

The Agent Lineup

  • 🔬 The Head Analyst: The data purist. Grounds proposals strictly in match history, player matchups, boundary dimensions, and venue run-rates. Proposes the initial tactical move based on max-probability outcomes.
  • 😈 The Devil’s Advocate: The risk-taker. Contrarian mindset focused on exploiting dew factors, pitch behavior, and psychological pressure. Aggressively challenges the Analyst’s every move.
  • 👑 The Virtual Captain: The pragmatic leader. Synthesizes the debate, weighs resources (like remaining overs), evaluates the game phase, and delivers the final, definitive decision.

The Debate Flow

  1. Match State Input 2. Analyst proposes a move.
  2. Devil’s Advocate challenges and counters.
  3. Virtual Captain evaluates the conflict and makes the final call.
  4. The UI renders the fan-friendly commentary output alongside the raw debate log.

🌪️ Innovation: Real-Time Agentic Tooling

An AI is only as good as its data. To elevate Captain Cool, I equipped the agents with real-time API tools using Gemini Native Function Calling:

  1. Micro-Climate & Dew Prediction Engine: The Devil’s Advocate uses a Weather API to fetch live dew points, humidity, and wind speed. If humidity crosses 75% at a coastal venue like Chepauk, the agent dynamically calculates a “grip loss percentage” and forces the Captain to hold back spin bowlers due to a wet ball.
  2. Geospatial Stadium & Pitch Analytics: The Head Analyst uses a venue database to map boundary dimensions and pitch soil profiles. If the wind is blowing towards a short 60m boundary at Chinnaswamy, the agent explicitly restricts bowling into the wind and suggests specific field placements (like a deep point) to protect the vulnerability.

These tools guarantee the debate is mathematically grounded in the actual physics of the current match.

🔒 The Stack: Dual-Layer Auth & Modern Web

The platform isn’t just a backend script; it’s a fully realized web application with a sleek, monochromatic glassmorphism UI.

Authentication: I implemented a Dual-Layer Auth Gateway converging Web3 and Web2:

  • 🦊 Web3: MetaMask (window.ethereum) handshake for decentralized identity.
  • 🔥 Web2: Firebase Auth for classic Email/Password access.

Users configure their own Gemini API key via a secure settings dashboard, ensuring keys are safely held in the application state and never logged to the client console.

Tech Stack Breakdown:

  • Frontend: React 19, Vite 8, Tailwind CSS 4, Framer Motion
  • Backend: Hono.js on Bun (for blistering fast API routes)
  • AI Orchestration: Google Gemini 2.5 Pro, Agent Development Kit (ADK)
  • IDE: Google Antigravity (Vibe-coded the entire session here!)

🎮 A Live Scenario Walkthrough

What does this look like in action? Let’s say you input this state into the Brain Room:

Innings 2, Over 15.2, 42 runs needed off 28 balls. Big hitter on strike. Left-arm spinner has 1 over left. Dew is actively setting in. Venue: Wankhede.

The Debate Visualizer outputs:

🔬 The Head Analyst: “Brings on the left-arm orthodox spinner. Matchup data shows the batsman struggles with away spin.”

😈 The Devil’s Advocate: “Object. The ball is soaking wet due to heavy dew. The spinner will slip, lose control of length, and release pressure. Bring on the express pacer for cross-seam deliveries instead.”

👑 The Virtual Captain: “Debate closed. We save the spinner for the long-boundary side later. The pacer bowls over the wicket, targeting hard lengths into the pitch.”

🏅 Final Thoughts & Shoutouts

Building a multi-agent orchestrated system in just 3 hours was an absolute adrenaline rush. It proved to me that the future of AI development isn’t just about crafting the perfect prompt; it’s about building ecosystems where specialized AI models can reason, argue, and utilize tools autonomously.

A massive shoutout to #gdgcloudpune, especially @antrixsh_gupta **, **Pratik Kale, and the entire organizing team. The problem statement was brilliant, the vibe was electric, and events like this push the developer community to level up in incredible ways.

Cricket is a captain’s game. Now AI captains it too. 🏆

👇 Check out the code and try it yourself:

Let me know in the comments what you think of multi-agent architectures or how you would have set the field for the final over! 🏏🔥

SpecPeek vs Figma Dev Mode: Share Design Specs Without Paid Developer Seats

Figma is the industry standard for product design. Its Dev Mode is the obvious way to hand designs off to developers — if the developers have paid seats.

But the picture is more nuanced than “free viewers get nothing.” Figma’s free viewer actually covers a fair amount of inspection. The question is where the free experience ends, what Dev Mode adds, and whether there’s a simpler path for teams that just need to share specs externally.

This article is an honest comparison. We’ll walk through exactly what developers can inspect for free in Figma, what requires Dev Mode, and where SpecPeek fills the gaps — without overstating what any tool does.

What Figma’s free viewer actually gives you.

Let’s start with what’s genuinely available to someone with free, view-only access to a shared Figma file. This is more capable than many people assume.

A free viewer can:

  • See the full design — every page, frame, and layer on the canvas
  • Select elements and inspect properties — width, height, position, fills, strokes, effects, opacity, border radius, auto-layout properties, all visible in the Properties panel
  • See typography details — font family, size, weight, line height, letter spacing, text strings
  • See colors, styles, and variables — as exposed through the Properties panel, including design-system references
  • Measure distances — select one object, hold Alt/Option, hover over another to see spacing. Measurements snap to the nearest container or sibling group in the hierarchy, not to individual nested elements within those groups
  • Copy CSS code — right-click any element → Copy as → Copy as code → CSS (also iOS and Android). Copies a flat list of CSS properties for the selected element only — no selector, no class name, no child elements, no design token references. Useful as a quick reference, but not paste-ready code
  • Export assets — select layers or frames and export as PNG, SVG, JPG, or PDF
  • Comment — pin comments on the canvas, mention teammates, have conversations

That’s not nothing. A developer with free access to a Figma file can inspect most visual properties and even grab CSS snippets via the right-click menu.

Where the free experience falls short

The gaps show up when a developer needs more than basic property inspection:

No dedicated code panel. Free viewers can copy CSS via right-click, but there’s no persistent code panel showing generated CSS alongside the design. In Dev Mode, the Inspect tab displays CSS, iOS, and Android code in a dedicated panel that updates as you select different elements. The right-click workaround works, but it’s slower — one element at a time, no way to scan properties at a glance.

No Tailwind output. Figma generates CSS, iOS, and Android code. It doesn’t generate Tailwind classes at all — not in the free viewer, not in Dev Mode. For teams using Tailwind (a significant share of the frontend ecosystem), this means manual translation from CSS values to utility classes.

No annotations. Designers can add annotations to elements in Dev Mode — notes, interaction descriptions, implementation guidance. Free viewers cannot see these annotations. The designer’s intent stays invisible to anyone without a paid seat.

No version history comparison. Free viewers see the current state of the design. They can’t compare what changed between versions, see visual diffs, or understand what was modified since the last handoff. In practice, this means Slack threads asking “what changed?” and back-and-forth with the designer to confirm which elements were updated.

No box model visualization. Dev Mode shows a visual box model diagram for selected elements — padding, margin, and content areas displayed as nested rectangles, similar to browser DevTools. Free viewers see the raw padding/margin values in the Properties panel but don’t get the visual diagram.

No component playground. Dev Mode lets developers interact with component variants — toggling states, changing properties, seeing how a component responds. Not available to free viewers.

No ready-for-dev workflow. The status system (marking frames as ready for development), change notifications, and integration with tools like Jira and GitHub are all Dev Mode features.

The seat cost problem

For teams that need those Dev Mode features, the cost is per-developer:

Seat type Professional plan Organization plan Enterprise plan
Full seat $16/mo $55/mo $90/mo
Dev seat $12/mo $25/mo $35/mo
Collab seat $3/mo $5/mo $5/mo
View seat Free Free Free

Prices shown are for annual billing. Monthly billing is 20–25% higher — e.g. $20, $15, and $5 for Full, Dev, and Collab seats on Professional.

For a stable in-house team, this math is straightforward and spending increases linearly with team size. However, it gets complicated with external collaborators. Freelancers who join for a sprint. Agency developers building a client’s product. QA engineers who need to verify spacing. Each person who needs full inspection means another seat to provision, manage, and deprovision.

And there’s a structural constraint that makes this worse: Figma’s permissions are tied to the file and the team. Sharing a design with an external developer means giving them access to your Figma workspace — even if you only want them to see one specific frame. To share a single frame in isolation, the designer needs to duplicate it into a separate file and share that, otherwise the developer sees everything in the main workspace.

How SpecPeek works differently

SpecPeek takes a different approach: the designer publishes specific frames, anyone with the link inspects them.

  1. Designer installs the SpecPeek Figma plugin. Select one or multiple frames — not a whole file, not a page, just the specific frames you want to share. Click publish. The plugin extracts the node tree, CSS properties, and layout data, encrypts everything client-side, and uploads the encrypted spec.

  2. SpecPeek generates a shareable URL. A unique link to view.specpeek.com pointing to the specific frames and encryption key. The designer sends it to anyone — Slack, email, Jira ticket, wherever.

  3. Anyone opens the link in any browser. No Figma account. No Google sign-in. No signup of any kind. They see the design rendered with interactive inspection — click any element to see its properties. The viewer shows structured properties for every selected element (dimensions, spacing, fills, typography, layout), a CSS tab with selector-ready code including class names, full component tree, and design token references, and a Tailwind tab with utility classes. Every value is click-to-copy. A visual box model diagram shows padding, margins, and content dimensions at a glance.

The key differences from Figma’s model:

Frame-level sharing, not file-level. A designer shares exactly the frames they want — not the entire file with every page, draft, and exploration. This avoids exposing work-in-progress designs or sensitive parts of the project, and keeps the developer focused on what’s relevant.

No account required. Figma requires at minimum a Figma account or Google sign-in to view shared files. SpecPeek requires nothing — the link is the access.

End-to-end encryption. Design data is encrypted in the Figma plugin before it leaves the designer’s machine. The server stores encrypted blobs it can’t read — your specs are protected even from SpecPeek itself.

Feature-by-feature comparison

Here’s what developers actually get at each level — Figma free viewer, Figma Dev Mode, and SpecPeek:

Capability Figma free viewer Figma Dev Mode (paid) SpecPeek
View design on canvas ✅ ✅ ✅
Select and inspect element properties Properties panel Full Inspect panel with box model Full Inspect panel with box model
See dimensions, position, spacing ✅ ✅ ✅
See fills, strokes, effects ✅ ✅ ✅
See typography details ✅ ✅ ✅
Styles and variable references ✅ ✅ ✅
Measure distances ✅ ✅ ✅
CSS code output Right-click copy (properties only, no selector) Dedicated code panel Dedicated tab (selector, component tree, token refs)
Tailwind output ❌ ❌ ✅
Designer annotations ❌ ✅ ✅
Version history & comparison ❌ ✅ ✅ With onion-skin overlay
Change tracking (added/modified/deleted, commit messages) ❌ ✅ ✅
Component playground ❌ ✅ ❌
VS Code integration ❌ ✅ ❌
MCP server for AI agents ❌ ✅ ❌
Ready-for-dev workflow ❌ ✅ ❌
Jira / GitHub integration ❌ ✅ ❌
Export assets ✅ ✅ ❌ Not yet
Share specific frames only ❌ Shares whole file ❌ Shares whole file ✅
Requires Figma account Yes Yes No
Requires paid seat No (but limited) Yes ($12–35/mo) No
End-to-end encryption ❌ ❌ ✅

Where Figma is the stronger choice

Deep integration. Dev Mode lives inside Figma. Developers can switch between design and inspection without leaving the tool. For teams where developers are already inside Figma files daily — collaborating, commenting, using the component playground — Dev Mode is the natural extension.

Component playground. Developers can interact with component variants directly — toggling states, changing properties. SpecPeek shows the component as-designed; it doesn’t offer interactive variant exploration.

VS Code extension and MCP server. Figma integrates with VS Code and exposes design context to AI coding agents through its MCP server. These are deep platform capabilities that a third-party viewer can’t replicate.

Prototyping and flows. For understanding transitions, animations, and user flows, those live in Figma’s prototyping layer.

Asset export. Figma lets developers export layers, frames, and components as images. SpecPeek doesn’t support asset export yet.

Mature ecosystem. Thousands of plugins, established integrations with project management tools, and years of refinement.

Where SpecPeek is the better fit

Zero-friction access. No account. No sign-in. No seat. No invitation to a workspace. The developer clicks a link and starts inspecting. This matters most for contractors, agencies, client handoffs, and cross-functional stakeholders who don’t need (or want) a Figma account.

Frame-level precision. Designers publish exactly the frames developers need — not the entire file. An agency handing off a login screen doesn’t expose the whole design system, drafts, and explorations alongside it. This is both more convenient and more secure.

Tailwind CSS output. No translation step from CSS values to utility classes. For the large share of teams using Tailwind, this removes a manual and error-prone conversion.

Annotations without Dev Mode. Designers add annotations in the plugin; developers see them in the viewer. The designer’s intent is visible to anyone with the link — not gated behind a paid seat.

Version history and visual comparison. SpecPeek shows full version history with commit messages, tracks added/modified/deleted elements, and offers an onion-skin overlay for visual comparison between versions. This is comparable to what Figma Dev Mode offers — but available to anyone with the link.

Faster navigation and measurement. SpecPeek’s viewer is purpose-built for spec inspection. Measuring distances is more natural — select an element and hover over any other element or group to see the distance, no modifier keys needed. In Figma, Alt+hover measures to the nearest container in the hierarchy, not necessarily the element you’re pointing at. Navigation is also streamlined because the interface does one thing: show specs.

Predictable cost model. One subscription for the designer who publishes. Unlimited viewers inspect for free. No per-viewer cost, no seat management.

Who should use what

Use Figma’s free viewer if your developers just need basic property inspection — colors, dimensions, typography — and the right-click CSS copy is enough. If they already have Figma accounts and the file is shared internally, the free viewer handles the basics.

Use Figma Dev Mode if your developers work inside Figma daily, you have a stable team with predictable seat counts, and you need the full developer toolchain — component playground, VS Code integration, ready-for-dev workflow, and project management integrations.

Use SpecPeek if you share specs with people outside your Figma organization — contractors, client teams, agency partners. If your developers need Tailwind output. If you want to share specific frames without exposing your entire Figma workspace. If you need version history and annotations available to anyone with a link. Or if you want the fastest path from “design is ready” to “developer has full specs” without any account provisioning.

Use both if you have an in-house team on Figma (designers and core developers use Dev Mode internally) and also need to share specs externally. SpecPeek doesn’t replace your internal Figma workflow — it gives your specs a life outside the Figma file.

Getting started

If you want to try SpecPeek alongside your existing Figma workflow:

  1. Run the SpecPeek plugin in any Figma file and select the frames you want to share
  2. Click “Publish” — the plugin generates a shareable URL
  3. Send the link to any developer — full spec access in seconds, no account needed

SpecPeek has a 14-day free trial with no credit card required. If external design handoff is a pain point for your team, publish one spec link and see whether the workflow fits.

Disclosure: I’m building SpecPeek, so this comparison comes from the perspective of the product we’re creating. I’ve tried to keep the Figma comparison fair and specific.

Why I Still Choose Ruby on Rails in the Age of AI

Hello, I’m Haruku, a freelance developer.
After recently starting my own company, I’ve found myself thinking more about what kind of web framework fits the age of AI.

I entered the world of computer science through Ruby, and since then I’ve launched side projects using popular technologies such as TypeScript (Next.js, Nuxt.js), Go, and Rust.
But in the end, I realized that Ruby and Ruby on Rails fit my use case best.

And that judgment has not changed even in 2026, when AI coding has become far more advanced.

In this article, I’d like to explain why I decided to keep using Ruby in the age of AI.

For context, I plan to use Rails for every business I start through my company going forward. Please read this as one perspective from someone who is, in a sense, a “Rails believer.”

Why I Use Rails

A Strongly Opinionated Community

Many of you have probably heard the term CoC. Yes, “Convention over Configuration.”
When starting a new Rails project, the framework forces many decisions that developers would otherwise have to make from scratch. Developers simply follow those conventions.
Developers who use Rails always code with this concept of CoC in mind.

For example, if you want to create a new model tied to a database, you only need to type:

rails new model Post title:string body:text

in your terminal, and Rails will automatically create the table, the model layer, and the related tests.

When I first learned programming and touched Rails, I wasn’t particularly impressed by this.
But when I later tried Next.js and Nuxt.js, I was shocked to realize that this was not the norm.

In many other frameworks, even the ORM for connecting to the database and the testing library are things you have to introduce yourself.

I genuinely wondered why so few other languages had truly “batteries-included” frameworks.

Even worse is that there is often no correct answer for “architecture,” including file structure.

Where would you place the file that defines a User model?

In Rails, of course, it goes in /app/models/user.rb.
But in TypeScript, Go, or Rust? Where do you put it? Do you just create your favorite location???

When hundreds of these decisions pile up, developers feel stressed even just reading the code.

Eventually, they set off on a journey to discover “what good architecture is,” spending time and mental energy on things like Clean Architecture or Onion Architecture that produce no profit at all. (Yes, that was me in the past. lol)

Rails’ strong philosophy is sometimes criticized as “inflexible” or “not suitable for certain cases.”

But unless you are planning a very large-scale project from the beginning, it probably won’t be a problem.

DHH, the creator of Rails, has pointed out that most modern web development is CRUD—that is, applications of simple database reads and writes—and has openly stated that Rails is fully capable within that scope.

This is highly compatible with AI coding.

For example, suppose an AI goes off the rails and writes logic inside the User model that sends an HTTP request. (They do this often.)

In terms of whether it runs or not, it probably will.

But sending HTTP requests is clearly not the responsibility of the model layer. The role of the model layer is to handle database reads/writes and relationships.

MVC image wiki

The more rotten code increases, the harder it becomes for both humans and AI to maintain the code or add new features.
And AI takes zero responsibility for the code it generates.

That is why, in the age of AI, a strongly opinionated Ruby on Rails becomes a strength.

Specialized for Small-Scale Development

You may have heard people say, “Large companies like GitHub and Shopify use Rails! That’s why you should use Rails!”

That is true, but I’d rather argue that Rails’ real strength shines in smaller, elite teams—say, one to five people.

DHH wrote a book called Rework.

Rework on Amazon

It argues that you can make enough profit with a small team without raising huge amounts of VC funding or hiring employees at scale.
This book influenced my life more than any other.

And I believe this approach of pursuing profit with a small team fits perfectly in the AI age.

Since LLMs became widespread, people have started saying that “a one-person unicorn company will emerge within a few years.”

Going forward, I believe engineering teams will continue to shrink, and the default competition will be about how quickly and how many products you can build and launch until you find PMF.

In other words, we are entering an era where small teams launch many products.

Rails is a framework DHH created for himself.

It is specialized for quickly launching from a business perspective, adding features, getting market feedback, and rapidly repeating that cycle.

The rails g scaffold command is the clearest example of this.

About three weeks have passed since I started my company, and by using only two free days per week—roughly ten days total—I was able to build a product and now have a realistic path to release it next week.

This is possible precisely because of Rails’ strong philosophy based on CoC and the community that supports it.

I don’t think it would have gone that way with something like Next.js. (I can imagine days spent fighting server components and caching.)

As a framework for a small elite team using AI to run the PDCA cycle, I don’t think there is anything more suitable.

And as shown by the large companies mentioned earlier, Rails can also scale properly when used appropriately.

More Than 20 Years of History

Ruby on Rails was released in July 2004. It already has more than 20 years of history!

As you know, for generative AI to produce more accurate results, the quality and quantity of the training data are prerequisites.

As an engineer, I fully understand the desire to build products with “modern” technologies.
But from the perspective of getting a business off the ground, you should absolutely avoid that. The reason is that the probability of failure increases.

When reviewing AI-generated code, haven’t you ever replied, “That was true for an old version, but the latest version is different”?

Rails has been used with consistent rules for more than 20 years, so it almost never outputs wildly incorrect code.

This high level of trust is one of the reasons I love Ruby.

Simple and Easy to Understand

Take a look at recent release notes from well-known frameworks:

  • “Server Components added?” Weren’t you the ones generating HTML only on the client side?
  • “Caching is now easier to handle?” Why do I have to think about caching in addition to what I’m eating tomorrow?
  • “We fixed a vulnerability!” Oh no, my company’s server was used for Bitcoin mining.
  • “This rendering feature is only supported on ◯ercel!” What a wonderful challenge to the open-source culture.

Honestly, I think many people are tired of recent web trends.

If that sounds like you, please come to the Ruby and Rails community. It’s quiet here.

For those who know nothing about Rails, here’s a quick summary of recent Rails trends:

  • Hotwire (Stimulus and Turbo) becoming the default: You can implement SPA-like UI experiences with the familiar Rails development style, while writing almost no JavaScript.
  • Hotwire Native: By applying WebView technology, you can build native-like iOS and Android apps simply by writing normal Rails.
  • Kamal: If you can prepare your own computer, such as a VPS, you can deploy in just a few steps. (It’s very easy and cheaper to operate than AWS or GCP!)
  • Production-ready SQLite and Solid Stack: Rails has moved closer to a setup that can be operated without relying on external infrastructure like PostgreSQL or Redis. (Of course, not relying on external services also makes it cheaper!)

These trends are extremely consistent: “Less is more.”

Despite its overwhelming history, I truly love Ruby on Rails as a framework that continues to evolve in a simpler and smarter direction.

Responses to Common Criticisms of Rails

I’ve talked about my love for Rails so far, but finally, I’d like to summarize my own responses to common criticisms of Rails.

It’s not a statically typed language. The lack of type completion is painful.

→ Whether a language is static or dynamic does not change the value delivered to users. Of course, each has its pros and cons.
But I believe Ruby’s flexibility supports Rails’ “OMAKASE for now” culture.
As for type completion, I don’t think you need to place too much importance on it. The existing ruby-lsp works well enough, and DHH himself apparently doesn’t use editor completion in the first place.
Today we also have completion through GitHub Copilot, so from the perspective of “starting a new business,” I don’t think this is a problem.

Ruby code tends to get messy.

→ This can be said of any language. Engineers with low development ability can write messy code in Ruby, TypeScript, Rust, or anything else.
The reason people often claim that “Ruby code gets messy” may be because Ruby is one of the relatively beginner-friendly languages.
Rather than turning it into a language issue, I think we should focus on how to write readable code, how to design databases appropriately, and how to name variables properly.

Also, based on my experience, senior engineers actually tend to write more verbose and harder-to-read code.
I don’t know why, but one hypothesis is that executives and managers throw unnecessarily complex requirements at “reliable senior engineers,” and the code that satisfies those requirements becomes messy as a result.

So rather than Ruby code itself being dirty, I think the unnecessary complexity and ambiguity of business requirements creates the impression that “Ruby/Rails code is somehow hard to read.”

But it’s not popular these days, right?

→ That is a painfully valid point. Ruby’s popularity is steadily declining.
If your goal is to “learn a programming language and increase your salary,” TypeScript is probably the safer choice.
I hope Ruby will be reevaluated.
Because from a business perspective, it is the best language and framework.

Ruby’s poor performance is terrible!

→ Do you want to drive an F1 sports car to the nearest supermarket?
I’m fine with a used compact car.

Twitter escaped from Rails!

→ And then it became Elon Musk’s toy!

I’m Developing a Rails Boilerplate for Solopreneurs

Because I love Rails, want to succeed with my own business using Rails, and want to launch as many products as possible, I’m developing a Rails boilerplate for solopreneurs that specializes in MVP development.
It will be released soon.
If you’re interested, I’ll distribute it for free to testers, so please send an email to [email protected] with the subject “Template Request.”

(Update)
The beta version is no longer available!
The official version can still be purchased at super-rails.com.


Super Rails | Rails Boilerplate for Solopreneurs

A production-ready Rails boilerplate with Devise, Avo, Stripe, ViewComponent and more. Skip the setup, ship your MVP in days.

favicon
super-rails.com

Have a great development life!

Demystifying the Browser Render Cycle: useLayoutEffect, requestAnimationFrame, and Layout Thrashing

As React developers, we often live in a comfortable world of state, props, and virtual DOMs. React handles the heavy lifting of updating the UI, and we rarely have to think about how pixels actually hit the glass.

However, when you need to build high-performance animations, drag-and-drop interfaces, or perfectly positioned tooltips, the abstraction leaks. You suddenly find yourself staring at weird UI flashes, dropped frames, or laggy layouts.

To fix these issues, you have to master three things: the Browser Render Cycle, React’s useLayoutEffect, and the native requestAnimationFrame API.

In this deep dive, we will map out exactly how these pieces fit together, explore the mechanics of Layout Thrashing, and learn how to write buttery-smooth UI code.

1. The Anatomy of a Single Browser Frame

To understand why our code can slow down a website, we first need to look at the browser’s daily routine. Modern screens refresh 60 to 120 times per second. That means for a 60Hz monitor, the browser has exactly 16.6 milliseconds to process code and redraw the screen for a single frame.

When a piece of JavaScript alters something on a webpage, it kicks off a strict, linear pipeline called the Browser Critical Rendering Path.

  1. JavaScript: JavaScript executes and mutates the DOM tree structure or changes CSS classes.
  2. Style (Recalculate Styles): The browser matches CSS selectors against the new DOM structure to figure out which rules apply to which elements.
  3. Layout (Reflow): The browser calculates the geometry of the page. It figures out exactly how many pixels wide an element is, where it sits relative to its parent, and how it impacts surrounding elements.
  4. Paint: The browser fills in the pixels. It draws backgrounds, text colors, borders, shadows, and images. This happens across multiple separate “layers” in memory.
  5. Composite: The browser takes all those separate layers, layers them in the correct $Z$-index order, and sends them to the GPU to be drawn onto your monitor.

2. Where React and the Event Loop Fit In

When you write React code, your component updates run inside the JavaScript phase of the Event Loop. However, the Event Loop handles different types of code execution using distinct queues.

Let’s look at the absolute chronological timeline of a single frame execution when a React state change occurs:

Phase A: The Execution Window

  • Macrotask Execution: The loop picks up a standard task (like a user clicking a button).
  • The Microtask Flush: As soon as that task finishes, the browser pauses everything to empty the Microtask Queue. This is where React’s Render and Commit phases live. React updates its internal state, calculates the virtual DOM, and writes the new nodes to the real DOM.

Phase B: The useLayoutEffect Interrupt

  • Because useLayoutEffect is synchronous, it fires immediately after React writes to the real DOM, but inside this same JavaScript execution window. The browser’s layout and paint steps have not happened yet.

Phase C: The Render Pipeline

Once all JavaScript and microtasks are completely finished, the browser thread takes control to prepare the visual frame:

  • requestAnimationFrame (rAF): The browser executes any queued rAF callbacks. This is the last chance for JavaScript to run right before the browser calculates geometry.
  • Style & Layout: The browser calculates the positions and sizes of the elements.
  • Paint & Composite: Pixels hit the screen.

Phase D: The Post-Paint Window

  • useEffect Fires: Now that the user is looking at the updated screen, React asynchronously shoots off your standard useEffect hooks in the background so they don’t block visual performance.

3. The Showdown: useLayoutEffect vs requestAnimationFrame

Both tools are used to synchronize code with the display, but they serve completely different masters.

Feature useLayoutEffect requestAnimationFrame (rAF)
Ecosystem React-specific hook. Native Browser Web API.
Execution Timing Post-DOM mutation, Pre-Paint (Microtask window). Right before the browser’s native Style/Layout steps.
Blocking Behavior Synchronous. Halts the browser from printing pixels until it finishes. Asynchronous. Runs inside the browser’s natural frame pacing.
Primary Use Case Fixing visual flickers by measuring/adjusting layout before rendering. High-performance, continuous UI animations and transitions.

The “Next Frame” Catch with rAF

If you schedule a requestAnimationFrame inside a standard event listener, it runs in that same frame’s pipeline.

However, if you schedule a rAF inside useLayoutEffect, you are calling it at a very late stage in the JavaScript window. The browser has already locked in its pipeline plan for the current frame. As a result, the rAF callback is pushed to the next frame.

If you use rAF to change styles inside useLayoutEffect, the user will see the unadjusted layout for exactly one frame (a visual flicker) before the rAF updates the styles on the next frame.

4. Understanding Layout Thrashing

To optimize animations, you must avoid the silent performance killer: Layout Thrashing.

By default, the browser is incredibly lazy—and that’s a good thing. When you write code like this:

div1.style.width = '200px';
div2.style.height = '300px';

The browser doesn’t calculate the layout twice. It simply marks its current layout data structure as “dirty”. It waits until all your JavaScript code is done, and then performs a single, optimized Layout calculation right before painting.

What is Forced Synchronous Layout?

Layout Thrashing happens when you force the browser to perform this layout calculation over and over again inside a single block of JavaScript code. This occurs when you Write a style change, and then immediately Read a layout property.

Consider this code running inside a hook:

// ❌ LAYOUT THRASHING
const w1 = element1.offsetWidth;       // 1. Browser layout is clean, returns value instantly.
element1.style.width = (w1 + 10) + 'px'; // 2. DOM is mutated. Layout is now marked "DIRTY".

const w2 = element2.offsetWidth;       // 3. BOOM! Browser needs to return an accurate number,
                                        //    but layout is dirty. It is FORCED to pause your JS 
                                        //    and run an emergency, synchronous Layout calculation.
element2.style.width = (w2 + 10) + 'px'; // 4. Layout is marked dirty AGAIN.

If you repeat this pattern inside a loop or across multiple components, you force the browser to compute the geometry of the entire webpage multiple times in a few milliseconds. This ruins your 16.6ms frame budget, causing severe animation lag (jank).

5. Why Reading Values in useLayoutEffect Causes Thrashing

When React executes your components, it processes them sequentially. If multiple child components contain useLayoutEffect hooks that individually read geometry and write style changes, they will trigger layout thrashing automatically.

// Component A (Child 1)
useLayoutEffect(() => {
  const width = boxRef.current.offsetWidth; // Read (Layout clean)
  boxRef.current.style.left = `${width}px`;  // Write (Layout becomes DIRTY)
}, []);

// Component B (Child 2)
useLayoutEffect(() => {
  const height = menuRef.current.offsetHeight; // Read (CRITICAL: Layout is dirty! Forces Reflow)
  menuRef.current.style.top = `${height}px`;    // Write (Layout becomes dirty again)
}, []);

Because useLayoutEffect runs synchronously right after React commits updates, the second component attempts to read from a DOM tree that was just marked “dirty” by the first component. The browser has no choice but to halt execution and perform an emergency reflow.

6. How to Avoid Layout Thrashing

The golden rule for maintaining smooth frame rates is simple: Batch your reads first, and batch your writes last.

The Correct useLayoutEffect Pattern (For Instant Adjustments)

If you need to adjust positioning before an element appears on screen to prevent a flicker, keep all reads together at the top of your effect, and apply your writes at the very end.

//  PERFECTLY BATCHED
useLayoutEffect(() => {
  // 1. Do all READS first while layout is clean
  const width1 = div1.offsetWidth;
  const width2 = div2.offsetWidth;

  // 2. Do all WRITES at the end
  div1.style.transform = `translateX(${width1}px)`;
  div2.style.transform = `translateX(${width2}px)`;
}, []);

The Hybrid Pattern (For Kicking off Smooth Animations)

If you are measuring an element to kick off a fluid, moving animation where a 1-frame delay is acceptable, you can read inside useLayoutEffect and defer the write to requestAnimationFrame.

This unblocks React’s render phase completely and hands the styling work directly to the browser’s native animation loop:

// ⚡ OPTIMIZED FOR ANIMATIONS
useLayoutEffect(() => {
  // Read when the layout is clean
  const currentHeight = elementRef.current.offsetHeight;

  // Defer the write to the browser's native animation timeline
  const frameId = requestAnimationFrame(() => {
    elementRef.current.style.height = `${currentHeight + 100}px`;
    elementRef.current.style.transition = 'height 300ms ease';
  });

  return () => cancelAnimationFrame(frameId);
}, []);

By respecting the browser’s render pipeline and keeping your reads and writes separated, you ensure your React applications remain highly responsive and visually seamless.“

Usecase: Positioning a tooltip around button

To position a tooltip perfectly relative to a target element (like a button), you must use useLayoutEffect.

If you use standard useEffect, the tooltip will render briefly at a default position (e.g., top-left corner or stacked awkwardly in the DOM) before jumping to the correct coordinates, causing a jarring visual flash.

useLayoutEffect lets you measure the button, calculate the math, and place the tooltip before the browser paints the screen, making the appearance completely seamless.

Here is a complete, production-ready example of how to implement this without causing layout thrashing:

`jsx
import React, { useState, useLayoutEffect, useRef } from ‘react’;

function TooltipButton() {
const [showTooltip, setShowTooltip] = useState(false);

// Refs to target the real DOM elements
const buttonRef = useRef(null);
const tooltipRef = useRef(null);

useLayoutEffect(() => {
// If the tooltip isn’t open, there is nothing to measure
if (!showTooltip || !buttonRef.current || !tooltipRef.current) return;

const button = buttonRef.current;
const tooltip = tooltipRef.current;

// --- STEP 1: BATCHED READS (Layout is clean) ---
// Get the bounding boxes of both elements from browser memory
const buttonRect = button.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();

// --- STEP 2: CALCULATE MATH (Pure JS calculation) ---
// Position the tooltip directly above the center of the button
const topPosition = buttonRect.top - tooltipRect.height - 8; // 8px spacing
const leftPosition = buttonRect.left + (buttonRect.width / 2) - (tooltipRect.width / 2);

// --- STEP 3: BATCHED WRITES (Layout becomes dirty) ---
// Apply styles directly to the DOM nodes right before the browser paints
tooltip.style.top = `${topPosition + window.scrollY}px`;
tooltip.style.left = `${leftPosition + window.scrollX}px`;

}, [showTooltip]); // Re-run calculations whenever the tooltip is toggled open

return (

ref={buttonRef}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
className=”trigger-button”
>
Hover over me

  {showTooltip && (
    <div 
      ref={tooltipRef} 
      className="tooltip-box"
      style={{
        position: 'absolute',
        backgroundColor: '#333',
        color: '#fff',
        padding: '8px 12px',
        borderRadius: '4px',
        fontSize: '14px',
        pointerEvents: 'none',
        whiteSpace: 'nowrap',
        // Start invisible or unpositioned so it doesn't flash 
        // if layout changes drastically
        top: 0,
        left: 0, 
      }}
    >
      Dynamic Tooltip Text!
    </div>
  )}
</div>

);
}

export default TooltipButton;

`

Why this follows the Rules perfectly:

  1. It Prevents the Flicker: When showTooltip becomes true, React inserts the tooltip markup into the DOM. Before the browser draws it, useLayoutEffect halts the paint train.
  2. It Follows the Read-then-Write Pattern: Notice how button.getBoundingClientRect() and tooltip.getBoundingClientRect() are invoked right next to each other at the very beginning. This forces exactly one synchronous layout calculation.
  3. It Finishes with the Writes: The styles are injected at the very end. The browser receives the style modifications, closes the useLayoutEffect execution window, and proceeds to draw the tooltip exactly where it belongs on its very first visual frame.

Usecase: Update progressbar as the user scrolls through a blog

Creating a smooth, high-performance Custom Scroll Animation or a Progress Indicator that moves as the user scrolls down a page.

Handling scroll events in JavaScript is notoriously heavy. If a user scrolls quickly, the browser can fire the onScroll event up to 100 times per second. If you try to update the DOM (like modifying the width of a progress bar) on every single scroll event, you will overwhelm the browser thread, causing Layout Thrashing and severe visual stuttering (“jank”).

By using requestAnimationFrame, you can decouple the rapid scroll events from the actual DOM updates, ensuring your animation code runs exactly once per browser frame.

Here is how you can use requestAnimationFrame in React to build a high-performance reading progress bar that fills up as the user scrolls down an article.

`jsx
import React, { useState, useEffect, useRef } from ‘react’;

function ScrollProgressBar() {
const [scrollProgress, setScrollProgress] = useState(0);

// A ref to keep track of whether a frame has already been requested.
// This acts as a lock mechanism to prevent spamming the browser.
const rafTick = useRef(false);

useEffect(() => {
const handleScroll = () => {
// If a frame calculation is already scheduled, ignore incoming scroll events
if (rafTick.current) return;

  // Lock: We are now scheduling a frame update
  rafTick.current = true;

  // Schedule the visual update right before the next browser paint
  requestAnimationFrame(() => {
    const totalHeight = document.documentElement.scrollHeight - window.innerHeight;

    if (totalHeight > 0) {
      const progress = (window.scrollY / totalHeight) * 100;
      setScrollProgress(progress);
    }

    // Unlock: The frame has painted, we can accept a new scroll update now
    rafTick.current = false;
  });
};

// Listen to the window scroll event
window.addEventListener('scroll', handleScroll, { passive: true });

// Clean up the event listener on unmount
return () => window.removeEventListener('scroll', handleScroll);

}, []);

return (

{/* The Progress Bar Container /}

position: ‘fixed’,
top: 0,
left: 0,
width: ‘100%’,
height: ‘5px’,
backgroundColor: ‘#e0e0e0’,
zIndex: 1000
}}>
{/ The Animated Filling Bar */}

height: ‘100%’,
width: `${scrollProgress}%`,
backgroundColor: ‘#007bff’,
// Notice we don’t need a CSS transition here!
// rAF updates fast enough to feel naturally smooth.
}} />

  {/* Placeholder content to make the page scrollable */}
  <div style={{ padding: '20px', lineHeight: '2' }}>
    <h1>High-Performance Scroll Tracking</h1>
    <p style={{ height: '2000px' }}>Scroll down to see the progress bar animate...</p>
  </div>
</div>

);
}

export default ScrollProgressBar;

`

Why requestAnimationFrame is the Perfect Fit Here

  1. It Implements “Throttling” Automatically: By using the rafTick.current boolean lock, we ignore the dozens of extra scroll events fired by the browser between frames. We only compute the scroll math when the browser is actively ready to draw a new frame.
  2. Perfect Frame Alignment: If the user is running a 144Hz gaming monitor, requestAnimationFrame will naturally execute 144 times a second. If they are on a 60Hz phone, it executes 60 times a second. The animation effortlessly self-corrects to match the hardware’s native refresh rate.
  3. Power Saving: If the user switches tabs or minimizes the browser, requestAnimationFrame automatically pauses itself in the background. Standard setTimeout loops or raw scroll events keep chugging along in memory, draining the device’s battery unnecessarily.

Making creative LUTs safer for Rec.709 video

A strong creative LUT can look great on the footage it was designed around.
That does not mean it should hit every source the same way.

This was the problem behind the latest Filmtone Desktop release.

Filmtone has built-in creative Looks named Stone, Urban, and Noir. They are
designed to do more than a tiny color nudge: they can move contrast, hue,
saturation, glow, softness, and the optical finish of the image.

That is useful when the source is ready for a creative transform.

It is less useful when the source has already been rendered for display.

The source is part of the color decision

Rec.709 footage is often already opinionated.

It may already have:

  • display contrast baked in;
  • saturation pushed into a consumer-friendly range;
  • highlights compressed into a smaller space;
  • skin and sky colors that are usable, but not very flexible.

If a full creative LUT is applied directly on top of that, the failure mode is
easy to recognize:

  • highlight color breaks;
  • skin shifts too far;
  • saturation feels harsh;
  • dark color separation collapses;
  • lowering the whole LUT intensity makes the result safer, but also makes the
    Look less useful.

The tempting fix is simple:

If the source is Rec.709, reduce LUT intensity.

That helped, but it was not enough.

It made the transform less aggressive, but it did not change the shape of the
transform. The same hue and saturation moves were still there, just blended
back toward the source.

What changed in Filmtone Desktop 1.12

Filmtone Desktop 1.12 adds dedicated Rec.709-safe variants for the built-in
Stone, Urban, and Noir Looks.

On Rec.709, SDR BT.709, Display P3 SDR, or unknown display-referred sources,
the Desktop renderer now chooses a safer Look variant instead of only lowering
the full Look intensity.

The intent is not to make the result flat.

The intent is to keep the useful parts:

  • glow;
  • softness;
  • grain and finish controls;
  • before/after comparison;
  • playback review before export.

And pull back the parts that break fastest on already-rendered footage:

  • strong hue travel;
  • saturation spikes;
  • aggressive highlight color shifts;
  • compression moves that were tuned for a different source state.

Full Looks still matter

This does not mean every source should be treated like Rec.709.

If the footage is normalized through an explicit Log or camera profile path,
Filmtone keeps the full Creative Pack 01 Look behavior.

That distinction matters.

The product should not flatten all sources into one generic safe path. A Log
clip waiting for normalization is not the same starting point as an iPhone or
camera file that has already been rendered for display.

Filmtone now keeps those paths separate:

source profile / source state -> base grade -> optical finish -> creative Look decision -> export

Preview and export must agree

The other important part is less visible, but more important for trust.

Preview, still export, video export, and video composition rendering all resolve
the same source-aware built-in Look variant.

That means if a Rec.709-safe preview is shown, the export path uses the same
decision.

For a finishing tool, that matters more than having a long feature list. The
basic promise is:

What you judge in playback should be the thing you export.

Where this fits

Filmtone is not a full editing suite replacement.

It is a smaller tool for a smaller moment:

  1. Open a clip.
  2. Choose a Preset or Look.
  3. Compare while the footage moves.
  4. Adjust only what the footage asks for.
  5. Export on Mac when the result is ready.

The Rec.709-safe variants make that path more reliable for the kind of footage
people already have: phone clips, camera files, SDR exports, and footage that
is technically usable but not yet finished.

If you work with video and LUTs, I would like feedback on whether this split
matches how you think about source material.

Try Filmtone:

Product Hunt launch will follow after this article has collected some early
feedback.

When Ping Doesn’t Quite Work as Expected

I was trying to troubleshoot a network issue the other day and I found myself staring at a ping command that just wouldn’t work.

I was trying to ping a website, but it kept saying ‘unknown host’. I was confused – I knew the website was up and running, so why couldn’t I ping it?

It turns out, the issue was with the DNS resolution. The ping command relies on the DNS to resolve the hostname to an IP address.

If the DNS can’t resolve the hostname, the ping command will fail.

To fix the issue, I used the ‘dig’ command to check the DNS resolution.

I realized that the DNS server I was using wasn’t able to resolve the hostname. I switched to a different DNS server and voila! The ping command started working.

The takeaway from this experience is that networking issues can often be complex and multi-layered.
It’s not always just a simple matter of ‘it works’ or ‘it doesn’t work’.
Sometimes, you need to dig deeper (pun intended) to understand what’s really going on.

What’s your go-to command for troubleshooting network issues?

uBlock Origin Extension with Dia Browser

Recently, I moved from using Arc browser to Dia browser to try the integrated AI features. The change that I first noticed was that uBlock Origin extension is no longer available on Dia browser, a chromium based browser.

It turned out to be the new version of Chrome, 140 has turned on the following features:

  • ExtensionManifestV2Unsupported
  • ExtensionManifestV2Disabled

Solution

In order to get around it perfectly, I refined the solution a few times.

1st iteration

A fast immediate workaround is open terminal on Mac and execute

/Applications/Dia.app/Contents/MacOS/Dia 
  --disable-features=ExtensionManifestV2Unsupported,ExtensionManifestV2Disabled

It opens the Dia browser with the two enabled features disabled. Then, go https://chromewebstore.google.com/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm to download the no longer supported uBlock origin extension.

2nd iteration

Hooray! It worked just fine! Wait! Hang on! Isn’t it means I need to open Dia browser through terminal every single time?! It’s quickly becoming inconvenient. I need a one-button-click solution!

Automator to save the day!

I created an Automator application to run the script.

/Applications/Dia.app/Contents/MacOS/Dia --disable-features=ExtensionManifestV2Unsupported,ExtensionManifestV2Disabled >/dev/null 2>&1 &

It is important to have >/dev/null so that it redirects stdout (normal output) to /dev/null.

Then 2>&1 redirects stderr (error output) to wherever stdout (1) currently goes.

Finally the last & is to run the process in the background so your terminal/script continues immediately. It is important that the script does not block waiting for the process to exit.

Beautify the application icon

Look at the default automator icon makes me sad. 🙁

I found that it’s actually pretty straightforward to do!

  1. Right click the actual Dia application icon
  2. Select “Get info”
  3. Click on the application icon at the bottom and press Cmd + c to copy to clipboard.
  4. Right click the new automator application icon
  5. Select “Get info”
  6. Paste the copied Dia icon
  7. Job done!

Final iteration

Happiness only lasted for a few days until I found another issue.

I have an habit to click the Dia icon to focus back to the opened Dia window. Don’t ask me why I didn’t it this way, it’s just my preference! 😛

The problem is when I click the Dia icon again, the script tries to open Dia application instead of focusing the current window. A prompt then shows up to tell me off Dia is already opened!

To get around this, I finally updated the automator script to check if Dia is already opened. If yes, activate and focus the current Dia window. Otherwise, open a new Dia application.

if ! pgrep -x "Dia" >/dev/null; then
/Applications/Dia.app/Contents/MacOS/Dia --disable-features=ExtensionManifestV2Unsupported,ExtensionManifestV2Disabled >/dev/null 2>&1 &
else
osascript -e 'tell application "Dia" to activate'
fi

osascript is a macOS command-line tool that lets you run AppleScript from Terminal, shell scripts, or Automator.

Hooray!! I finally managed to get ublock origin extension working on Dia browser seamlessly.

Caveats

  • This is not a permanent solution to have ublock origin extension installed and working on Dia or new chromium based browsers since future Chrome updates may still block or break this workaround.

  • New ublock origin updates might be limited, incompatible, or unavailable under this setup.

The Review Bottleneck: Why Faster Code Generation Isn’t Faster Delivery

The numbers nobody wants to talk about

The AI Engineering Report 2026 analyzed telemetry from 22,000 developers across more than 4,000 teams. The top-line metrics are impressive: epics completed per developer are up 66%, task throughput is up 34%, and PR merge rates are climbing.

But look one layer deeper and the story changes completely.

Median time in PR review is up 441%. Average time spent in code review is up nearly 200%. Pull request sizes have grown 51%. And 31% more PRs are merging with zero review — not because teams chose to skip it, but because reviewers can’t keep pace with the volume.

The report calls this pattern “Acceleration Whiplash.”

The bottleneck moved

For decades, writing code was the slowest step in the software delivery pipeline. A developer opened one or two PRs a day, and a teammate reviewed them over coffee. Review kept up because there wasn’t that much to review.

AI changed the first step. Developers with AI tools now produce five or six PRs a day. But a reviewer can still only handle the same number they always could. The pipeline is no longer balanced.

Faros AI analyzed data from more than 10,000 developers and found a 98% increase in PR volume. The result: PR review time went up 91%, even though code generation itself got faster.

This shift hits senior engineers hardest. A 2025 study found they spend an average of 4.3 minutes reviewing AI-generated suggestions, compared to 1.2 minutes for human-written code. It’s not that they’re slower — it’s that AI-generated code requires a different kind of review. You’re no longer validating correctness. You’re judging necessity. Does this abstraction earn its weight? Would the team want to maintain this defensive code six months from now?

That takes more cognitive effort per PR, not less — at the exact moment volume is exploding.

What actually helps

The answer isn’t skipping review or rubber-stamping AI-generated code. It’s getting smarter about where review effort goes.

Not every PR carries the same risk. A one-line config change and a 500-line refactor touching authentication logic should not receive the same scrutiny. Risk-based triage — automatically scoring PRs by diff size, CI status, sensitive file changes — lets reviewers spend their limited attention where it matters.

Visibility matters too. When PRs are scattered across dozens of repositories with no unified view, stale reviews go unnoticed. Tools like Code Board aggregate PRs from GitHub and GitLab into a single Kanban board specifically to make aging and queue imbalance obvious at a glance.

GitHub is also responding. In April 2026, they launched native stacked PR support through a CLI extension called gh-stack, aimed at breaking large changes into reviewable layers.

The real metric

High-performing teams review PRs within 4 hours. If your average exceeds 24 hours, that’s likely your biggest hidden bottleneck — and it cascades through your entire development process.

The organizations that win in 2026 won’t be those generating code fastest. They’ll be the ones who deliver value fastest — and that means fixing the step that’s actually stuck.

Building a Multi-Agent AI Cricket Simulator in Vanilla JS

Github Repo Link – https://github.com/Forkzen/CaptainCool

Have you ever wondered how different cricket captains would handle the exact same high-pressure situation? Would MS Dhoni’s ice-cool logic prevail, or would Virat Kohli’s sheer aggression win the day?

I decided to find out by building Captains HUD, a multi-agent AI simulator powered by Google Gemini! 🏏🤖

The Concept

The idea was to create a “Sports Broadcast HUD” where different AI agents debate cricket strategy in real-time. I wanted an architecture that included:

  • The Stats Analyst: Evaluates pitch conditions and calculates a “Pressure Index”.
  • The Strategist: Takes on the persona of a famous captain (Dhoni, Kohli, Rohit, or Gambhir) and makes a tactical call.
  • The Devil’s Advocate: A critical agent that finds the fatal flaw in the Strategist’s plan.
  • The Commentary Desk: Dynamic play-by-play commentary from legends like Harsha Bhogle and Ravi Shastri.

The Tech Stack

To keep things lightning-fast and visually striking, I avoided heavy frameworks and stuck to:

  • Vanilla HTML/CSS/JS: For a pure, responsive broadcast HUD.
  • Google GenAI SDK: To interact with the Gemini models.
  • Canvas Confetti: For some explosive visual feedback! 🎉

The “Mega-Prompt” Optimization

Initially, I had each agent calling the Gemini API sequentially. This created latency and quickly hit Free Tier rate limits.

The Solution: I refactored the backend into a single “Mega-Prompt” using strictly structured JSON schema output. I asked the AI to simulate the entire debate in one go, returning a JSON object containing the internal monologue, the decision, the challenge, the rebuttal, and the final commentary.

On the frontend, I simply staggered the rendering of this JSON payload using setTimeout delays. The result? A single API call that feels like a live, real-time debate to the user!

Key Takeaways

Building this project taught me that you don’t always need complex orchestration frameworks for multi-agent systems. Sometimes, a well-crafted prompt with strict JSON schemas and clever frontend staggering can create a highly immersive AI experience.

If you’re building an AI app, focus on the user’s perception of the AI thinking! Simple CSS animations and delayed rendering can turn static text generation into a thrilling live event.

Have you built any fun multi-agent systems? Let me know in the comments!

I Got Tired of Opening Dozens of URLs Every Day, So I Built a Windows Tool

If your workflow involves reviewing staging environments, you probably know the pain:
opening dozens of URLs, waiting for each page to load, capturing screenshots, organizing them into slides… and repeating this every single day.

I reached a point where I realized I was spending more time documenting work than actually doing work.

So I decided to automate the entire process.

I built a Windows tool called Open URLs.

It lets you:

Open multiple URLs at once

Capture webpages automatically

Export results directly to PowerPoint

Store everything locally without external servers

What started as a personal workflow tool eventually became something I wanted to release publicly.

If you regularly review websites, prepare reports, or document UI changes, this tool might save you a lot of time.

You can try it here:
• Open URLs Lite — Microsoft Store
• Open URLs Pro — Microsoft Store