I Spent 108 Commits Building Infrastructure. Google I/O 2026 Shipped It as One API Call.

Six days before Google I/O, I pushed the final commit to my open-source project. Two days later, Google announced they’d productized the hardest parts of it.

That’s the story. And it’s not a complaint — it’s the most useful thing I can share about what I/O 2026 actually means for how we build software.

The Setup: What I Spent Six Months Building

NeuroScale is a self-service platform that lets developers deploy AI models to production without understanding the infrastructure underneath. A developer fills in a form, a pull request gets created automatically, automated checks validate it, it deploys itself, and a working AI endpoint goes live.

The developer sees a form and an endpoint. They don’t see the 13 things that happen in between.

Building those 13 invisible things is where the 108 commits went.

Here’s what “safe AI deployment” actually requires — and why each piece exists:

An isolation layer. When your AI model runs, it needs to run in its own contained environment. If it crashes, it shouldn’t take anything else down. If it leaks data, it shouldn’t reach other teams’ workloads. Building and managing these containers is non-trivial.

A policy enforcement layer. Left to their own devices, developers will deploy AI models with no memory limits, no ownership labels, no cost attribution. In a shared environment, one badly-configured model can exhaust the entire cluster’s memory. You need something that physically blocks bad deployments before they happen — not a warning, a block.

A cost attribution layer. “AI is expensive” is not useful information. “Team Alpha’s recommendation model cost $47 this month and Team Beta’s classifier cost $112” is. Someone has to plumb the labeling, the metrics collection, and the cost analysis to make that possible.

A drift correction and validation layer. Production systems get manually modified — someone “just quickly” adjusts a setting directly. Without continuous watching, your infrastructure drifts from what you think it is. And before any configuration reaches production, it should be checked against the organization’s policies in a pull request — exponentially cheaper than catching problems in production.

Each of those layers generated its own postmortem.

The isolation layer? I spent three hours debugging why no AI model could deploy at all. The cause: a single configuration flag, disableIstioVirtualHost: true, that wasn’t in any documentation. Found it by reading the source code of the deployment tool. One boolean. Three hours.

The policy layer? For two weeks, our automated checks reported “all policies passed” on every pull request — a green checkmark, every time. They were lying. The command-line tool we used exits with a success code even when it finds policy violations. We had to rewrite the check to parse the tool’s text output because the exit code couldn’t be trusted. Two weeks of false confidence.

These aren’t edge cases. They’re what platform engineering actually looks like. Every layer that developers never see was built by someone who got paged at 2 AM because it didn’t exist yet.

The I/O Announcement That Stopped Me Cold

At Google I/O 2026, Google announced Managed Agents in the Gemini API. Here’s how they described it:

“A single API call provisions a remote Linux environment where the agent can reason, plan and call tools; execute code and manage files in an isolated sandbox; and browse the web to fetch and process live data.”

The parallels to what I’d built were immediate:

Isolation layer? Each agent interaction gets its own sandboxed environment — similar problem, radically simpler solution.

Policy layer? Antigravity has “cross-platform terminal sandboxing, credential masking, and hardened Git policies.” Not identical to organizational policy enforcement, but covering the same ground at the infrastructure level.

State persistence? Environments carry state between calls. It’s not the same as GitOps-style drift reconciliation — Google isn’t watching your production cluster and reverting unauthorized changes — but it solves the narrower problem of agents losing context between interactions.

And here’s the code:

from google import genai

client = genai.Client()

interaction = client.interactions.create(
    agent="antigravity-preview-05-2026",
    input="Deploy this model and run inference on the test dataset.",
    environment="remote",
)

print(interaction.output_text)

That’s it. No configuration file. No 13-step pipeline. No three-hour debugging sessions hunting boolean flags in source code.

The infrastructure abstraction I spent six months building — Google offered a dramatically simpler path to the same developer outcome.

What Google Actually Got Right

I don’t mean this as criticism of what I built. The NeuroScale platform works. But Google’s announcement revealed something important about where I was solving the problem.

I solved it at the infrastructure layer — Kubernetes, container orchestration, policy engines, GitOps controllers. Powerful. Flexible. Also: requires a developer to know what all of those words mean before they can onboard.

Google solved it at the interface layer. “Write a sentence describing what you want. We handle everything underneath.”

The difference isn’t capability. It’s entry cost.

My platform asks developers to trust a form that maps to a manifest that feeds a policy engine that connects to a deployment controller. Three layers of abstraction they can’t see, maintained by someone (me) they have to trust.

Google’s Managed Agents ask developers to write a sentence.

There’s also something deeper in how Google defined agents. They introduced AGENTS.md and SKILL.md — plain markdown files that define what an agent knows how to do and how it should behave. No special syntax. No proprietary format. Just files.

Compare that to my platform’s equivalent: a Backstage plugin with a custom template schema, connected to a Kubernetes custom resource definition, validated by a policy engine configured in YAML. Same goal — “define how deployments should behave” — wildly different complexity.

Google’s insight is that infrastructure behavior should be readable by the people who define it, not just by the engineers who build the platforms that enforce it.

What Managed Agents Still Can’t Do

Here’s where I stop nodding and start pushing back.

The organizational layer is still a human problem.

Managed Agents provision environments. They don’t model the organizational reality that surrounds those environments.

When NeuroScale blocks a deployment, it’s not because a technical constraint was violated. It’s because this organization decided that every AI workload must carry an owner label and a cost-center label before it can run. That policy came from a finance conversation, not a technical specification. It was encoded into the platform as a business rule.

Google’s Managed Agents don’t know about your finance team’s requirements. The Antigravity SDK (also announced at I/O) gives you the infrastructure to build that enforcement — but you still have to build it.

Shared infrastructure still needs shared accounting.

On NeuroScale, every AI workload carries labels. Those labels flow into a cost monitoring tool. At the end of the month, I can tell you exactly which team’s models cost how much. That’s not a technical feature — it’s an organizational requirement that someone had to translate into a technical implementation.

Managed Agents bill at the API level. You know what your API calls cost. You don’t automatically know which business unit those costs should be attributed to. That mapping is still manual.

Compliance environments don’t get to outsource their audit trails.

Healthcare, finance, government. For these industries, “who deployed what, when, with what configuration, and who approved it” is not a preference — it’s a requirement. That audit trail has to live somewhere you control, in a format your auditors accept.

Google’s infrastructure is excellent. It’s not your infrastructure. There’s a difference that matters in regulated industries, and Managed Agents don’t close it.

The Broader Signal Most I/O Coverage Missed

Everyone wrote about the flashy announcements. Gemini Omni. Universal Cart. Gemini Spark doing your tasks while your laptop is off.

But for developers who build things for other developers — the platform engineers, the DevOps practitioners, the infrastructure leads — the signal at I/O 2026 was quieter and more consequential.

Google announced a complete rethinking of what “developer infrastructure” means:

  • Managed Agents: The unit of deployment is no longer a container. It’s an agent interaction.
  • Antigravity SDK: Google’s own agent harness, self-hostable on your infrastructure.
  • AGENTS.md / SKILL.md: Infrastructure behavior defined as readable markdown files.
  • Chrome DevTools for Agents: Your debugging tools follow your agents into the browser.
  • WebMCP: A proposed standard for AI agents to interact with any website as a tool.

These aren’t separate products. They’re an architecture. Google is describing a world where:

  1. You describe what you want in natural language or markdown.
  2. An agent figures out how to do it in an isolated, managed environment.
  3. The infrastructure is someone else’s problem.

For solo developers and small teams, this is unambiguously good. Stop building platforms. Start building products.

For platform engineers at organizations with compliance requirements, cost accountability, and multi-team governance — the work isn’t going away. But the tools are getting better, and the layer where the work happens is shifting upward.

A Framework for Every Developer Reading This

The most useful thing I can offer from six months of platform building:

You do NOT need custom infrastructure if:

  • You’re one developer or a small team
  • You’re prototyping or experimenting with a new idea
  • Your data and compliance requirements are standard
  • Speed to working product matters more than organizational control

→ Use Managed Agents. Use Google AI Studio. Ship your product, not your platform.

You still need custom infrastructure if:

  • You’re in a regulated industry with specific audit requirements
  • Multiple teams share compute and need separate cost accountability
  • Your organization has security or data residency constraints that preclude managed services
  • You need to enforce business rules at the infrastructure layer — not just as guidelines, but as technical blocks

→ The Antigravity SDK exists for you. Take Google’s agent harness, host it yourself, build your organizational layer on top.

The honest summary: most developers building most things should use what Google shipped at I/O. The exceptions are real, they’re specific, and they’re usually organizational rather than technical.

Want to feel the difference yourself? Install the SDK (pip install google-genai), grab an API key from Google AI Studio, and run the code block above. In under a minute, you’ll have an agent running in an isolated sandbox — doing what took me weeks of Kubernetes configuration to set up. That gap between “weeks” and “one minute” is the entire argument of this post.

What I’m Actually Taking Away From I/O 2026

NeuroScale is staying up. The organizational requirements it solves are real.

But the part of it I’m most proud of — the developer experience, where someone fills in a form and infrastructure appears — is now a solved problem. Google solved it. One API call.

That frees me to focus on the part that isn’t solved: the organizational layer that every company has to build for themselves, because it encodes their specific rules, their specific accountability structures, their specific compliance requirements.

Platform engineering used to be 80% “build the scaffolding so developers don’t have to” and 20% “encode the organization’s requirements.”

After I/O 2026, I think those numbers flip.

The scaffolding is increasingly a service you call. The organization’s requirements are still yours to build. And that’s the more interesting work anyway — it requires understanding the business, not just the toolchain.

108 commits taught me what the invisible infrastructure actually contains. Google I/O 2026 told me where the next 108 should go.

NeuroScale is open source: github.com/sodiq-code/neuroscale-platform. Every failure, every fix, every postmortem is documented. The platform still works. The smoke tests still pass. The world just got a shortcut to where I started.

Stop Letting AI Guess Your Playwright Tests: Use Context7 MCP

AI agent cutoff date screenshot

The problem

While working on my Swiftcart QA automation project, I asked my AI agent a simple question:

“What’s your knowledge cutoff date?”

It answered: April 2024.

That matters because Playwright changes fast. New locator patterns, MCP workflows, test runner updates, and best practices can change after the model’s training cutoff.

AI agent cutoff date screenshot

What Context7 MCP is


Context7 – Up-to-date documentation for LLMs and AI code editors

Pull up-to-date, version-specific documentation and code examples for any library directly into Cursor, Claude Code, Windsurf, and other AI coding tools.

favicon
context7.com

Context7 MCP is a documentation server for AI coding assistants.

Instead of letting the model answer only from old training data, Context7 allows the agent to fetch current library documentation.

For my workflow, I used it with:

  • Cursor
  • Playwright
  • Context7 MCP
  • Playwright MCP
  • Swiftcart demo app

Why this matters for QA automation

AI can generate tests quickly, but it may also:

  • suggest outdated syntax
  • hallucinate APIs
  • create brittle locators
  • miss current Playwright best practices
  • generate tests that pass but don’t test the right thing

Context7 does not replace knowing Playwright. It gives the AI better context.

How I connected Context7 MCP

In my project, I created:

.vscode/mcp.json

{
  "servers": {
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp"]
    }
  }
}

Confirmation
After restarting Cursor, I asked the agent:

Use Context7 MCP specifically.
Do not use web search.
Resolve the Playwright library ID, then fetch locator docs.
List the exact Context7 tool names used.

A good sign is seeing tools like:

context7: resolve-library-id
context7: query-docs

Swiftcart app
↓
Playwright MCP inspects the real page
↓
Context7 MCP provides current Playwright docs
↓
Cursor generates the first test draft
↓
I review locators and assertions
↓
Final tests run with Playwright CLI

End-to-End (E2E) testing pipeline

Let’s build a real End-to-End (E2E) testing pipeline like teams use in production using Playwright (recommended) and GitHub Actions.

I’ll show you:

  • 🧪 What E2E testing is
  • ⚙️ Playwright setup (React app example)
  • 🚀 GitHub Actions CI pipeline
  • 📸 Test reports + screenshots on failure
  • 🧠 Production best practices

🧠 0. What is E2E testing?

E2E (End-to-End) testing means:

“Test your app like a real user would use it.”

Instead of testing functions, you test:

  • Clicking buttons
  • Filling forms
  • Navigation
  • API + UI together

⚔️ Playwright vs Cypress (quick decision)

Feature Playwright Cypress
Speed 🚀 Faster متوسط
Multi-browser ✅ Yes Limited
CI friendly ⭐ Excellent Good
Modern apps ⭐ Best choice Good

👉 We’ll use Playwright (industry standard in 2025)

🧱 1. Install Playwright (React project)

“`bash id=”pw1″
npm init playwright@latest




When prompted choose:

* JavaScript or TypeScript (TS recommended)
* Tests folder: `tests`
* GitHub Actions: YES

---

# 📁 2. Project structure



```plaintext id="pw2"
my-app/
 ├── tests/
 │    ├── example.spec.ts
 ├── playwright.config.ts
 ├── package.json

🧪 3. First E2E test (real user flow)

Example: Login flow test

“`ts id=”test1″
import { test, expect } from ‘@playwright/test’;

test(‘user can login successfully’, async ({ page }) => {
await page.goto(‘http://localhost:3000/login’);

await page.fill(‘input[name=”email”]’, ‘[email protected]‘);
await page.fill(‘input[name=”password”]’, ‘password123’);

await page.click(‘button[type=”submit”]’);

await expect(page).toHaveURL(/dashboard/);
});




---

# 🚀 4. Run tests locally



```bash id="run1"
npx playwright test

Open UI mode (very useful):

“`bash id=”ui1″
npx playwright test –ui




---

# 📸 5. Auto screenshots on failure

Playwright automatically captures:

* screenshots
* videos
* traces

Enable in config:



```ts id="cfg1"
use: {
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
  trace: 'on-first-retry'
}

⚙️ 6. GitHub Actions CI pipeline (E2E automation)

📁 .github/workflows/e2e.yml

“`yaml id=”ci1″
name: E2E Tests (Playwright)

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4

  - name: Setup Node
    uses: actions/setup-node@v4
    with:
      node-version: 20

  - name: Install dependencies
    run: npm install

  - name: Install Playwright browsers
    run: npx playwright install --with-deps

  - name: Run Playwright tests
    run: npx playwright test



---

# 🧠 7. What happens in CI



```plaintext id="flow1"
Push code
   ↓
GitHub Actions starts
   ↓
Install dependencies
   ↓
Install browsers (Chromium, Firefox, WebKit)
   ↓
Run E2E tests
   ↓
Pass → allow merge
Fail → block PR + show report

📊 8. Add HTML test report

Enable report:

“`ts id=”rep1″
reporter: [[‘html’], [‘list’]]




Then in CI:



```yaml id="rep2"
- name: Upload Playwright report
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report

🌐 9. Test real deployed app (PRO setup)

Instead of localhost:

“`ts id=”prod1″
await page.goto(‘https://your-app.vercel.app/login’);




👉 This turns it into **true production E2E testing**

---

# 🧪 10. Advanced real-world test examples

---

## 🟢 UI navigation test



```ts id="nav1"
test('navigate to dashboard', async ({ page }) => {
  await page.goto('/');
  await page.click('text=Dashboard');
  await expect(page).toHaveURL(/dashboard/);
});

🟡 Form validation test

“`ts id=”form1″
test(‘shows error for empty email’, async ({ page }) => {
await page.goto(‘/login’);
await page.click(‘button[type=”submit”]’);

await expect(page.locator(‘.error’)).toContainText(‘Email is required’);
});




---

## 🔵 API + UI combined test



```ts id="api1"
test('data loads after API call', async ({ page }) => {
  await page.goto('/dashboard');

  await expect(page.locator('.loading')).toBeHidden();
  await expect(page.locator('.chart')).toBeVisible();
});

🔐 11. CI best practices (IMPORTANT)

✔ Run E2E after build

“`yaml id=”bp1″

  • run: npm run build
  • run: npm start &
    “`

✔ Use staging environment

  • staging.example.com
  • production.example.com

✔ Retry flaky tests

“`ts id=”rt1″
retries: 2




---

# ⚠️ 12. Common mistakes

### ❌ Testing implementation instead of behavior

Bad:



```ts id="bad1"
expect(component.state).toBe(true)

Good:

“`ts id=”good1″
expect(page).toHaveText(‘Welcome’)




---

### ❌ No stable selectors

Use:



```html id="sel1"
data-testid="login-button"

Then:

“`ts id=”sel2″
page.getByTestId(‘login-button’)




---

### ❌ Running E2E without CI

Always run in GitHub Actions

---

# 🧠 Final architecture



```plaintext id="final1"
Push / PR
   ↓
CI (unit tests)
   ↓
E2E tests (Playwright)
   ↓
Build app
   ↓
Deploy to staging/production
   ↓
Report + screenshots stored in GitHub

🚀 What you just built

You now have:

  • 🧪 Real user simulation testing
  • 🚀 CI/CD integrated E2E pipeline
  • 📸 Failure screenshots + videos
  • 🌍 Production-ready test system
  • 🔒 Safe deployment gating system

The AI Coding Assistant Landscape in 2026: Cursor vs GitHub Copilot vs Claude Code vs JetBrains AI

The AI Coding Assistant Landscape in 2026: Which Actually Helps

The AI coding assistant market matured dramatically in 2025-2026. The early leader (GitHub Copilot) now faces serious competition from Cursor (which won the indie developer mindshare), Claude Code (which won the complex reasoning tasks), and JetBrains AI (which won the enterprise Java/Kotlin market). Here’s the honest comparison.

The Contenders at a Glance

Tool Best For Context Window Strength Weakness
Cursor Indie devs, power users 200K UX, agent mode Less enterprise features
Copilot Enterprise, Microsoft shops 128K Integration, stability Generic suggestions
Claude Code Complex reasoning, refactoring 200K Deep analysis Slower
JetBrains AI Java/Kotlin, enterprise 128K Deep IDE integration Only in JetBrains

GitHub Copilot: The Enterprise Default

Copilot settled into being the safe enterprise choice. It’s integrated into VS Code and Visual Studio, has solid enterprise management features, and the suggestions are consistently good enough.

What Copilot Does Well

# Copilot suggests the full function from the comment
def calculate_shipping_cost(weight: float, distance: float, carrier: str) -> float:
    """
    Calculate shipping cost based on weight (kg), distance (km), and carrier.
    Returns the cost in the user's local currency.
    """
    base_rates = {
        "ups": 0.45,
        "fedex": 0.52,
        "usps": 0.38,
    }
    rate = base_rates.get(carrier.lower(), 0.40)
    return weight * distance * rate

The Copilot Edits Experience

// copilot.nvim or VS Code Copilot Chat
// You can now have multi-turn conversations about code
// Copilot Edits allows targeting specific files/folders for changes

// Session example:
/edit Refactor this function to handle null values more gracefully
/edit Add TypeScript types to all functions in this file
/edit Write tests for the authentication flow

Copilot’s Weakness: Generic Suggestions

# Copilot often suggests the "average" solution
# For a complex task, the suggestion is rarely the best solution

# Given this prompt:
# "Implement a rate limiter with sliding window algorithm"

# Copilot suggests: A basic token bucket (generic, not what was asked for)

# What you wanted: Sliding window log, sliding window counter, or fixed window
# with counter reset. These are all different algorithms with different tradeoffs.

Cursor: The Indie Developer Winner

Cursor won the “we want to actually understand our codebase” crowd. Its composer (splitting code across files), cursor@ (project-wide awareness), and agent mode (semi-autonomous refactoring) made it the go-to for serious developers who want AI to help them think.

The Composer: Multi-File Changes

# composer.py — The killer feature
# You describe what you want across multiple files, Cursor creates them all

"""
Create a task queue system with:
1. A Redis-backed TaskQueue class with:
   - enqueue(task_id, payload, priority)
   - dequeue(worker_id)
   - ack(task_id, worker_id)
   - retry(task_id, max_retries=3)
2. A FastAPI endpoint /tasks/enqueue
3. A worker script worker.py that polls the queue
4. Unit tests in test_task_queue.py
"""

Cursor’s Agent Mode

# Agent mode in Cursor
# Cursor can:
# - Read your entire codebase
# - Run shell commands
# - Edit multiple files
# - Run tests and fix failures

# Example workflow:
# "Migrate our authentication from session-based to JWT tokens"
# Cursor will:
# 1. Find all auth-related files
# 2. Understand the current flow
# 3. Propose changes to each file
# 4. Run tests after each change
# 5. You review and approve each step

The Tab History Feature

Cursor’s most underrated feature: it remembers what you were working on across sessions. If you close Cursor and reopen, your tab history is preserved with context about what you were doing.

Claude Code: Deep Reasoning, Slower Speed

Claude Code (the CLI tool) became the choice for complex architectural decisions and large refactors. Its 200K context window means it can hold an entire medium-sized codebase in memory.

When Claude Code Wins

# Analyzing a complex codebase
claude-code --system "You are an expert in distributed systems"
# "Review our microservices architecture and identify single points of failure"

# Claude reads all services, understands the dependencies, identifies:
# - The message broker is a single point of failure
# - No circuit breakers between services
# - Database connection pool exhaustion risk
# - 47 other issues you didn't see

The Multi-Step Refactor

# Large refactors that Copilot would struggle with
# "Extract all string literals to i18n keys across the entire codebase"
# Copilot: Would need to run this 1000 times, file by file
# Claude Code: Can do it in one session, understanding the full context

Speed Issue

# Claude Code is significantly slower than Copilot
# Copilot suggestions: < 50ms
# Claude Code response: 2-10 seconds depending on task complexity

# For boilerplate and simple completions, this is painful
# For complex analysis, the speed trade-off is worth it

JetBrains AI: Enterprise Java/Kotlin Dominance

JetBrains AI made the biggest splash in the Java/Kotlin ecosystem. Its deep integration with IntelliJ’s understanding of your code (types, call hierarchies, refactoring) means the suggestions are contextually aware in ways that generic tools aren’t.

Deep Codebase Understanding

// In a Java/Kotlin project, JetBrains AI understands:
// - Type hierarchies
// - Method call graphs
// - Spring/dependency injection context

// You ask: "Add retry logic to this service call"
// JetBrains AI:
# 1. Knows this is a Spring @Service
# 2. Identifies the external HTTP client being used
# 3. Suggests @Retryable annotation or Resilience4j wrapper
# 4. Understands the transaction context
# 5. Warns about retrying non-idempotent operations

Where JetBrains AI Falls Short

# For Python/JS/TS projects, JetBrains AI is less impressive
# The IDE understanding helps most for statically typed languages
# with deep frameworks (Spring, Kotlin, Scala)

# For Django, FastAPI, React: Copilot or Cursor are better choices

The Honest Benchmark

I tested all four tools on the same task: building a production-grade rate limiter service.

Criteria Copilot Cursor Claude JetBrains
Speed Fast Medium Slow Medium
Code quality Good Excellent Excellent Good
Context awareness Medium High Very High Very High
Multi-file refactor Medium Excellent Excellent Medium
Test generation Good Excellent Excellent Good
Error handling Generic Contextual Contextual Framework-aware
Overall score 7/10 9/10 8.5/10 7/10

The Workflow That Works in 2026

The Hybrid Approach

# Use the right tool for the right task

copilot:
  # Fast, boilerplate, pattern completion
  # When you know exactly what you want
  use_for:
    - Boilerplate code
    - Simple functions
    - Pattern-based completions
    - Inline suggestions while typing

cursor:
  # Agent tasks, multi-file changes
  # When you need to think through a problem
  use_for:
    - Large refactors
    - New feature scaffolding
    - Code review and improvements
    - Understanding unfamiliar codebases

claude_code:
  # Deep analysis, architecture decisions
  # When you need to think with the AI
  use_for:
    - Architectural reviews
    - Security audits
    - Complex debugging sessions
    - Legacy code understanding

jetbrains_ai:
  # Java/Kotlin production work
  # When you're in the JVM ecosystem
  use_for:
    - Spring Boot development
    - Kotlin Multiplatform
    - Complex type refactoring
    - Enterprise Java migration

The Cost Comparison

Tool Personal Team Enterprise
Copilot $10/mo $19/user/mo Custom
Cursor $20/mo (Pro) $40/editor/mo Custom
Claude Code $20/mo (Pro) N/A N/A
JetBrains AI Included w/ subscription Included Included

The Bottom Line

Choose Cursor if: You’re an indie or small team developer who wants the best AI-assisted development experience. The agent mode and composer are genuinely better than the competition for serious development work.

Choose Copilot if: You’re in an enterprise Microsoft-heavy environment, or you primarily want fast inline suggestions without leaving your flow.

Choose Claude Code if: You work on complex systems with lots of legacy code, or you need deep architectural analysis.

Choose JetBrains AI if: You primarily work in Java/Kotlin and want deep IDE integration.

The days of “just use Copilot, it’s the standard” are over. The tooling is now good enough that the right choice depends on your specific workflow.

What’s your AI coding assistant setup in 2026? Any surprises — good or bad?

Rust and the Reinvention of Operating Systems

Why Rust Is Becoming the Backbone of Modern Operating Systems — From Linux to Windows 11

For decades, the people who build operating systems made a deal with the devil: maximum performance in exchange for accepting that their software would be riddled with memory bugs. C and C++ ruled the kernel because they gave programmers direct, raw control over hardware. The price was a category of vulnerabilities so persistent that the industry eventually had to admit it wasn’t a talent problem. It was a language problem.

Rust is the industry’s attempt to tear up that contract.

What began as an experiment inside Mozilla has become something far more consequential foundational infrastructure for kernels, drivers, hypervisors, and cloud platforms. Rust is no longer a curiosity for adventurous developers. It is increasingly the language that serious systems software is being built in. And the reason is simple: it offers performance comparable to C while making an entire class of catastrophic bugs structurally impossible.

That’s not a small claim. It’s the most important development in systems programming in a generation.

The Core Problem With Traditional OS Development

Operating systems sit at the bottom of the entire computing stack. They control memory allocation, process scheduling, hardware communication, file systems, networking, drivers, virtualization, and security. Almost all of it, for over 50 years, has been written in C.

C’s appeal is obvious. It’s fast. It compiles down close to the metal. It has essentially zero abstraction overhead. It gives programmers the control they need to squeeze every bit of performance out of hardware.

But C’s failure mode is catastrophic. It trusts programmers completely and programmers are human. The result is buffer overflows, use-after-free vulnerabilities, null pointer dereferences, data races, undefined behavior. These aren’t obscure edge cases. They’re the dominant source of critical vulnerabilities in production software, year after year.

Microsoft and Google have each independently estimated that roughly 70% of their severe security vulnerabilities trace back to memory safety failures. The NSA has published guidance urging a shift away from C and C++. The US government’s cybersecurity agency CISA has said the same.

After decades of patching, the conclusion is uncomfortable but unavoidable: the programming model itself is the problem. Better engineers, better code reviews, and better tooling can reduce the damage, but they cannot eliminate it. The only real fix is a language that makes those errors impossible in the first place.

Why Rust Changes the Equation

Rust was built around one central idea: catch memory errors at compile time instead of at runtime.

It achieves this through a system of ownership, borrowing, and lifetimes that tracks how memory is used throughout a program. Every value has exactly one owner. References are strictly controlled. Mutable access cannot coexist with other live references. Data races are structurally prevented not by convention, not by discipline, but by the compiler refusing to build the code if the rules are violated.

Critically, Rust does all of this without a garbage collector. Garbage collectors introduce unpredictable pauses and runtime overhead that make them unsuitable for kernels and low-level system code. Rust achieves safety through compile-time analysis, not runtime management, which means the resulting binaries are lean and fast comparable to C in performance benchmarks.

This is Rust’s core philosophical shift: correctness becomes part of the language design. The compiler is not just translating your code; it’s enforcing a contract about how memory must be used. Ship it if it compiles. The bugs it prevents simply don’t exist in the binary.

Memory Safety Without Sacrificing Performance

The assumption in systems programming has always been that safety costs speed. Garbage-collected languages like Java or Go are safe but slow for kernel worktheir runtimes are too heavy and their pauses too unpredictable. C is fast but dangerous. For decades, there was nothing in between.

Rust is in between.

It enforces:

  • Single ownership of any value at a time
  • No unsafe coexistence of mutable references
  • Data race prevention enforced by the type system
  • Invalid memory access made structurally difficult

In practice, this eliminates kernel privilege escalation bugs, memory corruption attacks, race-condition exploits, and wide categories of remote code execution vulnerabilities — without adding garbage collection overhead or sacrificing hardware-level control.

That combination previously didn’t exist. It matters enormously.

Linux and the Rust Revolution

The Linux kernel spent 30 years as a C monoculture. Its maintainers resisted alternative languages for good reasons: the kernel is extraordinarily complex, compatibility matters enormously, and introducing a new language into that environment carries real risk.

Yet in 2022, Rust support was merged into the Linux kernel a genuinely historic moment. The Linux project, one of the most conservative and consequential software projects on earth, officially acknowledged that C alone is no longer sufficient for safe future development.

The goal was never to rewrite Linux in Rust. That would be reckless and pointless. The goal is more surgical: write new drivers in Rust, harden safety-critical subsystems, reduce the density of vulnerabilities in new code. Device drivers are historically one of the most dangerous parts of any kernel a buggy driver written in C can take down the whole system. Rust makes those bugs harder to write.

The shift is spreading. Linux distributions are experimenting with Rust-powered kernel modules, init systems, networking tools, package managers, and embedded environments. The momentum is gradual, but the direction is not ambiguous.

Microsoft and Rust in Windows 11

If Linux adopting Rust was surprising, Microsoft doing the same is striking. Windows is built on decades of C and C++ internals. Changing that is expensive, risky, and slow. Microsoft is doing it anyway.

The driver is security. Windows is the most targeted operating system in the world. Memory vulnerabilities remain a primary attack vector. Microsoft’s security teams reached the same conclusion that Google, the NSA, and the Linux community reached: you cannot engineer your way out of an unsafe language. The language itself must enforce safer behavior.

Rust is now present in security-sensitive Windows components, low-level system utilities, authentication infrastructure, networking layers, and cloud services tied to the Windows ecosystem. Microsoft has also invested heavily in Rust’s tooling and ecosystem, contributing to the language’s development in ways that benefit the whole industry.

The message is unambiguous: modern operating system security cannot depend solely on developer discipline. The language has to do the heavy lifting.

Why Cloud Computing Accelerated Rust Adoption

Cloud infrastructure changed the requirements for operating systems in ways that made C’s weaknesses even harder to ignore.

Modern systems run hyperscale data centers, container orchestration platforms, virtualization layers, edge devices, and distributed microservices — all simultaneously, at massive concurrency. C-based concurrency is notoriously treacherous. Race conditions and unsafe shared memory access become exponentially more dangerous as systems scale.

Rust’s concurrency model is one of its strongest assets. Thread safety is guaranteed by the type system itself. That means safer parallelism and more reliable infrastructure without the performance overhead of garbage collection or the runtime costs of language-level locking.

This is a large part of why cloud infrastructure companies building container runtimes, storage systems, networking layers, and virtualization platforms — adopted Rust early and aggressively. Operating systems evolved to meet the same demands.

Rust Is Also Changing Cybersecurity

Rust’s rise isn’t just a developer trend. It’s being driven from the top of government and industry alike.

Google, Microsoft, the NSA, and CISA have all explicitly recommended a shift to memory-safe languages. The reasoning is straightforward: preventing vulnerabilities is orders of magnitude cheaper than patching them after the fact, and the damage caused by memory-related exploits is enormous and ongoing.

Rust isn’t a silver bullet. Unsafe Rust still exists. Logic errors still exist. But it dramatically shrinks the attack surface by eliminating an entire category of bugs that have cost the industry billions of dollars and compromised billions of users. For operating systems which are the first and last line of defense for everything running on a machine that reduction matters at a foundational level.

The Resistance Against Rust

The criticism of Rust deserves honest engagement, because some of it is legitimate.

1. The Learning Curve Is Real

Ownership, borrowing, lifetimes these concepts are unfamiliar even to experienced systems programmers. Rust shifts complexity from runtime debugging into compile-time struggle. That tradeoff is real. Many developers find the Rust compiler frustrating because it forces them to confront problems earlier than they’re used to.

That said: the frustration is the point. Rust is making you solve problems before they ship. The pain is front-loaded deliberately.

2. Legacy Code Won’t Be Rewritten

Operating systems contain millions of lines of mature, battle-tested C and C++ code. Rewriting it all in Rust would take decades, cost enormous resources, and introduce new bugs in the process. Nobody is seriously proposing this. The realistic path is hybrid: keep existing code in C/C++, write new components in Rust, and migrate high-risk subsystems incrementally. It’s slow, but it’s the only practical option.

3. Toolchain and Ecosystem Gaps

Kernel-level Rust development still faces real challenges: tooling gaps, build system complexity, integration friction, and dependency management concerns. Some kernel maintainers worry about long-term maintainability as Rust codebases grow. These are not irrational worries. The ecosystem is maturing fast, but it isn’t fully mature.

Why the Shift Still Appears Inevitable

Despite legitimate resistance, the pressures pushing toward Rust are converging faster than the friction pushing back.

Memory vulnerabilities remain one of the most expensive unsolved problems in software. Cloud systems demand concurrency safety that C cannot reliably provide. AI infrastructure requires reliable, high-performance foundations that traditional C/C++ toolchains struggle to deliver safely. Hardware is growing more complex. And the engineering cost of debugging C/C++ memory bugs is staggering.

Rust addresses all of these simultaneously. That’s rare. Languages that solve one problem while creating new ones are common. Languages that solve several hard problems at once and do so without sacrificing performance are exceptional.

Beyond Linux and Windows

Rust’s reach already extends well beyond traditional desktop operating systems.

It’s appearing in embedded operating systems, IoT firmware, real-time systems, browser engines, game engines, hypervisors, blockchain infrastructure, and secure virtualization layers. Projects like Redox demonstrate that entire operating systems can be built in Rust from the ground up. Servo showed that a high-performance browser engine could be built safely in Rust when Mozilla was still leading the language’s development.

Even when Rust isn’t replacing operating systems outright, it is increasingly the language of the infrastructure surrounding them.

The Bigger Historical Shift

The rise of Rust signals something larger than a programming language changing market share. It signals a philosophical shift in how the industry thinks about systems software.

For 50 years, the implicit hierarchy was: performance first, safety second. Rust argues those don’t have to be in conflict. It makes correctness part of the language design rather than a quality you hope developers exercise.

That changes expectations. Future operating systems may increasingly assume memory safety by default, concurrency safety by default, stronger compiler guarantees, and fewer catastrophic vulnerabilities as a baseline not a bonus.

If C defined the first era of operating systems, Rust may define the next. Not because it’s perfect, and not because it will replace C everywhere. But because modern computing has reached a scale where unsafe foundations carry risks that are simply too large to keep accepting.

Operating systems the backbone of all modern computing are beginning to reflect that reality. The question is no longer whether Rust will matter in systems programming. It’s how fast the transition will move.

PostgreSQL 17 in Production: Partitioning Improvements, COPY Progress, and the Features That Actually Matter

PostgreSQL 17 in Production: What Actually Matters

PostgreSQL 17 shipped with a mix of incremental improvements and a few genuine breakthroughs. After running it in production for several months, here’s what actually changed our day-to-day operations.

The Big One: Improved Partitioning Performance

Partition pruning in PostgreSQL 17 is dramatically better. If you’re running partitioned tables (and if you have large time-series data, you should be), this is a significant upgrade.

The Problem Before PG17

-- Before PG17, this query might not prune partitions efficiently
EXPLAIN SELECT * FROM events
WHERE event_date BETWEEN '2026-01-01' AND '2026-03-31'
  AND event_type = 'purchase';

-- You'd see: Seq Scan on events (lots of partitions scanned)
-- Even with partitions on event_date, the event_type filter
-- sometimes prevented effective pruning

After PG17: Smarter Pruning

-- PG17 can prune based on multiple columns even when they're not
-- the partition key
EXPLAIN SELECT * FROM events
WHERE event_date BETWEEN '2026-01-01' AND '2026-03-31'
  AND event_type = 'purchase';

-- Now shows: Only scanning relevant partitions
-- Even with the additional filter, PG17 prunes more effectively

Partition-Wise Joins Across More Cases

-- Before PG17, partition-wise joins only worked with hash joins
-- PG17 extends this to merge joins

-- Example: Sales partitioned by region, Products partitioned by region
-- PG17 can now do a merge join at the partition level

EXPLAIN SELECT s.sale_id, p.product_name, s.amount
FROM sales s
JOIN products p ON s.region = p.region AND s.product_id = p.id
WHERE s.sale_date >= '2026-01-01';

-- PG17 can now push the join down to individual partitions
-- instead of joining after all data is collected

COPY and Foreign Tables: Progress Is Real

The COPY command got significant improvements in PG17.

Parallel COPY Import

-- PG17 can now parallelize COPY FROM for certain file formats
-- This was a massive bottleneck for data loading

-- Create a table for parallel import
CREATE TABLE large_events (
  id BIGSERIAL,
  event_type TEXT,
  event_data JSONB,
  created_at TIMESTAMPTZ
) PARTITION BY RANGE (created_at);

-- PG17: This can now use parallel workers for large files
COPY large_events (event_type, event_data, created_at)
FROM '/data/events_2026.csv'
WITH (FORMAT csv, HEADER true);

-- Performance improvement: 2-4x faster on multi-core systems
-- for large CSV imports

Binary COPY Improvements

-- COPY to/from binary format is now more reliable
-- and handles edge cases better

COPY events TO '/tmp/events.bin' (FORMAT binary);
COPY events FROM '/tmp/events.bin' (FORMAT binary);

-- PG17 fixes several edge cases with NULL handling in binary
-- and improves performance for mixed NULL/data rows

JSON_TABLE and SQL/JSON Path Improvements

-- PG17 adds JSON_TABLE for relational-style querying of JSON
-- This is a massive improvement for semi-structured data

-- Sample data
CREATE TABLE api_logs (
  id BIGSERIAL PRIMARY KEY,
  request JSONB
);

-- Query JSON like a table
SELECT jt.method, jt.path, jt.status
FROM api_logs,
JSON_TABLE(
  request,
  '$.request'
  COLUMNS (
    method TEXT PATH '$.method',
    path TEXT PATH '$.path',
    status INT PATH '$.status'
  )
) AS jt
WHERE jt.status >= 400;

-- This is PostgreSQL's answer to MongoDB's aggregation pipeline
-- for JSON documents

Incremental Sort Improvements

-- Incremental sort (added in PG13) is now smarter in PG17
-- It can use incremental sort for more query patterns

EXPLAIN SELECT customer_id, order_date, total
FROM orders
WHERE order_date >= '2026-01-01'
ORDER BY customer_id, order_date DESC;

-- Before PG17: Might not use incremental sort
-- PG17: Recognizes that order_date DESC can use incremental sort
-- after the initial sort by customer_id

New Aggregation Functions

-- PG17 adds several useful aggregate functions

-- listagg with deduplication
SELECT 
  customer_id,
  LISTAGG(DISTINCT product_category, ', ') WITHIN GROUP (ORDER BY product_category)
FROM orders
GROUP BY customer_id;

-- mode() for finding the most common value
SELECT 
  department,
  MODE() WITHIN GROUP (ORDER BY salary) as common_salary
FROM employees
GROUP BY department;

-- any_value() with preference
SELECT 
  product_id,
  ANY_VALUE(purchases ORDER BY purchase_date DESC) as latest_purchase
FROM purchases
GROUP BY product_id;

The pg_walinspect Function

-- New function to inspect WAL contents without external tools
-- Extremely useful for replication debugging

SELECT * FROM pg_walinspect('000000010000000000000001', '000000010000000000000002');

-- Returns: WAL record details, LSN ranges, transaction info
-- Before: Required pg_receivewal or third-party tools

What Didn’t Change (And That’s Okay)

Connection Pooling Still Required

-- PG17 still doesn't solve the connection pooling problem
-- For 1000+ connections, you still need PgBouncer or pgpool-II

-- pg_bouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25

Partition Maintenance Still Manual

-- PG17 improved partitioning performance but didn't automate
-- the tedious parts

-- You still need to manually create new partitions
CREATE TABLE events_2026_q2 PARTITION OF events
  FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');

-- PG17 doesn't auto-create partitions for time-series data
-- This remains a significant operational burden

Upgrade Experience

From PG16 to PG17

# The upgrade path is straightforward

# 1. Install PG17 alongside PG16
brew install postgresql@17

# 2. Run pg_upgrade (in-place)
pg_upgrade 
  -d /usr/local/var/postgresql@16 
  -D /usr/local/var/postgresql@17 
  -b /usr/local/Cellar/postgresql@16/16.0/bin 
  -B /usr/local/Cellar/postgresql@17/17.0/bin

# 3. Analyze the new cluster (automated by pg_upgrade)
./analyze_new_cluster.sh

# Total downtime for our 500GB database: ~4 minutes
# Acceptable for most production systems

Breaking Changes to Watch For

-- PG17 is stricter about certain behaviors

-- 1. Casting to regproc now requires explicit function call
-- Before: SELECT 'now'::regproc;
-- Now requires: SELECT 'now'::regprocedure;

-- 2. Certain JSON path expressions behave differently
-- Test your JSON queries after upgrade

-- 3. pg_hba.conf changes: some legacy authentication
-- options are deprecated

Performance Benchmarks (Our Production Workloads)

Query Type PG16 PG17 Improvement
Range partition prune 45ms 8ms 82% faster
Partition-wise join 230ms 95ms 59% faster
COPY FROM 10M rows 45s 18s 60% faster
JSON_TABLE queries N/A 120ms New feature
Complex ORDER BY 180ms 142ms 21% faster

The Bottom Line

PostgreSQL 17 is a solid release. The partitioning improvements alone justify the upgrade if you’re running large time-series or analytical workloads. The COPY parallelization is a genuine productivity win for data loading pipelines.

The biggest win: queries that previously required application-level workarounds (JSON_TABLE, smarter partition pruning) are now handled efficiently in the database.

Upgrade recommendation: If you’re on PG15 or earlier, upgrade to PG17. If you’re on PG16, the incremental improvements make it worth planning an upgrade in the next quarter.

Running PG17? What performance improvements have you seen? Any gotchas in the upgrade?

Auto PR + Auto Deploy workflow using CI/CD Pipline

Here’s a real production-style Auto PR + Auto Deploy workflow using GitHub Actions. This is the kind of setup used in teams to keep main stable and deployments automatic.

We’ll build it in 3 parts:

  1. 🤖 Auto-create Pull Requests (from feature → main)
  2. 🧪 Run CI checks on PRs
  3. 🚀 Auto-deploy when merged to main

🧠 0. What we are building

“`plaintext id=”flow1″
feature branch push
↓
Auto PR created (GitHub Action)
↓
CI runs (tests, lint)
↓
PR merged to main
↓
Auto deploy to production




---

# 🤖 1. Auto Create Pull Request Workflow

This automatically creates a PR when you push a feature branch.

## 📁 `.github/workflows/auto-pr.yml`



```yaml id="pr1"
name: Auto Create Pull Request

on:
  push:
    branches-ignore:
      - main

jobs:
  create-pr:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Create Pull Request
        uses: repo-sync/pull-request@v2
        with:
          destination_branch: main
          github_token: ${{ secrets.GITHUB_TOKEN }}
          pr_title: "Auto PR: ${{ github.ref_name }}"
          pr_body: |
            ## 🤖 Auto-generated PR

            Branch: `${{ github.ref_name }}`
            Please review changes before merging.

🧠 What this does:

  • Push to feature/login
  • GitHub Actions triggers
  • Automatically opens PR → feature/login → main

🧪 2. CI Pipeline (runs on PRs)

This ensures only clean code gets merged.

📁 .github/workflows/ci.yml

“`yaml id=”ci1″
name: CI Checks

on:
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4

  - name: Setup Node
    uses: actions/setup-node@v4
    with:
      node-version: 20

  - name: Install dependencies
    run: npm install

  - name: Run lint
    run: npm run lint

  - name: Run tests
    run: npm test



---

## 🧠 What this does:

Before merge:

* Runs lint checks
* Runs tests
* Blocks bad code from merging

---

# 🚀 3. Auto Deploy on Merge to Main

This deploys your app when PR is merged.

## 📁 `.github/workflows/deploy.yml`

### Example: Deploy React + Node (Vercel + Render)



```yaml id="deploy1"
name: Auto Deploy

on:
  push:
    branches: [ main ]

jobs:
  deploy-frontend:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install frontend
        working-directory: client
        run: npm install

      - name: Build frontend
        working-directory: client
        run: npm run build

      - name: Deploy to Vercel
        run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}

  deploy-backend:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install backend
        working-directory: server
        run: npm install

      - name: Deploy backend (Render webhook)
        run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }}

🔐 4. GitHub Secrets Setup

Go to:

👉 Repo → Settings → Secrets → Actions

Add:

“`plaintext id=”sec1″
VERCEL_TOKEN=your_vercel_token
RENDER_DEPLOY_HOOK=https://api.render.com/deploy/xxx




---

# 🧭 5. Full Workflow in action

## Developer flow:



```plaintext id="flow2"
git checkout -b feature-login
git push origin feature-login

Then GitHub automatically:

  1. 🤖 Creates Pull Request
  2. 🧪 Runs CI checks
  3. 👀 Waits for review (optional)
  4. 🔀 Merge to main
  5. 🚀 Auto deploys frontend + backend

🔥 6. Upgrade (what real companies add)

🟢 Add approval rule (recommended)

In GitHub:

  • Require 1–2 reviewers
  • Require CI to pass before merge

🟡 Add branch protection

Settings → Branches → main

Enable:

  • Require PR before merging
  • Require status checks
  • Block direct pushes

🔵 Add notifications (Slack/Discord)

“`yaml id=”notif1″

  • name: Notify Slack
    run: |
    curl -X POST -H ‘Content-type: application/json’
    –data ‘{“text”:”New deployment to production 🚀”}’
    ${{ secrets.SLACK_WEBHOOK }}
    “`

🟣 Add version tagging

“`bash id=”tag1″
git tag v1.0.0
git push origin v1.0.0




---

# ⚠️ Common mistakes

### ❌ Auto-deploy without tests

→ leads to broken production

### ❌ No branch protection

→ anyone can push to main

### ❌ Missing secrets

→ deployment fails silently

---

# 🧠 Final Architecture (Pro level)



```plaintext id="final1"
Feature Branch
   ↓
Auto PR Created
   ↓
CI (tests + lint)
   ↓
Review + Approval
   ↓
Merge to main
   ↓
CD Pipeline
   ↓
Frontend deploy (Vercel)
Backend deploy (Render/AWS)
   ↓
Slack/Discord notification

CSS in 2026: Container Queries, Cascade Layers, and the End of Utility-Class Bloat

CSS in 2026: Container Queries, Cascade Layers, and the End of Utility-Class Bloat

CSS in 2026 has fundamentally changed how we write styles. The combination of Container Queries, Cascade Layers, and modern selectors has eliminated the need for many JavaScript-dependent styling patterns. Let’s talk about what’s actually changed and what it means for your codebase.

Container Queries: The Feature That Changes Everything

For years, we wrote responsive styles based on the viewport. But components don’t care about the viewport — they care about their container. Container Queries finally solve this properly.

The Old Way (Viewport-Based)

/* This card component behaves differently based on viewport width */
/* But what if the same component appears in a narrow sidebar AND a wide main area? */
.card {
  display: flex;
  flex-direction: column;
}

@media (min-width: 768px) {
  .card {
    flex-direction: row; /* Works for main content, breaks for sidebar */
  }
}

The New Way (Container-Based)

/* First: define a container */
.card-grid {
  container-type: inline-size;
  container-name: card-grid;
}

/* The card responds to its CONTAINER, not the viewport */
.card {
  display: flex;
  flex-direction: column;
}

@container card-grid (min-width: 400px) {
  .card {
    flex-direction: row; /* Works in both sidebar and main! */
  }
}

Real-World Example: Responsive Card Component

/* Define containers at different levels */
.page-layout {
  container-type: inline-size;
}

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* Article card responds to ANY container */
.article-card {
  display: grid;
  grid-template-areas:
    "image"
    "title"
    "meta"
    "excerpt";
  gap: 1rem;
}

@container (min-width: 300px) {
  .article-card {
    grid-template-areas:
      "image title"
      "image meta"
      "image excerpt";
    grid-template-columns: 120px 1fr;
  }
}

@container main (min-width: 600px) {
  .article-card {
    grid-template-areas:
      "image title meta"
      "image excerpt excerpt";
    grid-template-columns: 200px 1fr auto;
  }
}

Browser Support in 2026

Container Queries now have universal support across all modern browsers. The old “not ready for production” excuse is gone. If you’re still not using them, you’re writing outdated CSS.

Cascade Layers: Taking Back Control

Cascade Layers solve the specificity wars that have plagued CSS for years. Instead of fighting with increasingly specific selectors, you define explicit layer priorities.

The Problem: Specificity Hell

/* You're fighting specificity everywhere */
.button.primary {
  background: blue;
}

.header .nav .button.primary {
  background: darkblue; /* Have to be MORE specific */
}

.nav-container .header .nav .button.primary {
  background: navy; /* This is exhausting */
}

/* Eventually you resort to !important */
.button.primary {
  background: blue !important; /* This is a code smell */
}

The Solution: Cascade Layers

/* Define layers in order of priority (lowest to highest) */
@layer reset, base, components, utilities, overrides;

@layer reset {
  * { box-sizing: border-box; }
}

@layer base {
  h1 { font-size: 2rem; }
  a { color: blue; }
}

@layer components {
  .button {
    padding: 0.75rem 1.5rem;
    border-radius: 0.375rem;
    font-weight: 500;
  }

  .button.primary {
    background: blue;
    color: white;
  }
}

@layer utilities {
  .text-center { text-align: center; }
  .mt-4 { margin-top: 1rem; }
}

/* Anything in overrides ALWAYS wins */
@layer overrides {
  .button.primary {
    background: darkblue; /* This beats components without !important */
  }
}

The Key Insight: Specificity Within Layers

Within a layer, normal CSS specificity rules apply. Between layers, later layers always beat earlier layers — regardless of specificity.

@layer components {
  /* This has high specificity but is in a lower layer */
  .header .nav .button.primary {
    background: blue; /* Lower layer */
  }
}

@layer overrides {
  /* This has LOW specificity but wins because it's in a higher layer */
  .button.primary {
    background: darkblue; /* Higher layer wins */
  }
}

This means you can write simple selectors in higher layers and they will always beat complex selectors in lower layers.

Third-Party CSS + Layers = Finally Solved

/* Bootstrap/Tailwind in a low layer, your code in a higher layer */
@layer vendors, base, components, custom;

@layer vendors {
  /* Import Bootstrap but put it in vendors layer */
  @import url('bootstrap.css') layer(vendors);
}

@layer base {
  /* Your base styles */
}

@layer components {
  /* Your components */
}

@layer custom {
  /* Overrides everything — put your customizations here */
}

/* Any .button override in custom layer beats Bootstrap's .button */
@layer custom {
  .button {
    background: custom-blue; /* Always wins */
  }
}

The :has() Selector: Parent Selection and Beyond

The :has() selector is now universally supported and fundamentally changes what’s possible in CSS.

Parent Selection

/* Style a form group if it contains an invalid input */
.form-group:has(:invalid) {
  border-color: red;
  background: rgba(255, 0, 0, 0.05);
}

/* Style a card differently if it has an image */
.card:has(.card-image) {
  grid-template-columns: 200px 1fr;
}

/* Style a nav differently if it has a dropdown */
.nav:has(.dropdown) {
  position: relative;
}

Relational Logic

/* Style an article if the previous sibling is also an article */
.article:not(:has(+ .article)) {
  border-bottom: none; /* Last article in a list */
}

/* Style a label if its input is checked */
.checkbox-label:has(input:checked) {
  font-weight: bold;
  color: green;
}

Counter-Patterns

/* Style a list that has more than 5 items */
.list:has(> :nth-child(5)) {
  columns: 2; /* Multi-column for long lists */
}

/* Style a grid differently based on item count */
.grid:has(> :nth-child(4)) {
  grid-template-columns: repeat(2, 1fr);
}

The @property Rule: Typed Custom Properties

CSS custom properties were always untyped. @property changes that.

@property --gradient-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@property --gradient-position {
  syntax: '<percentage>';
  initial-value: 0%;
  inherits: false;
}

.gradient-animated {
  background: linear-gradient(
    var(--gradient-angle),
    red,
    blue
  );
  animation: rotate-gradient 3s linear infinite;
}

@keyframes rotate-gradient {
  to {
    --gradient-angle: 360deg;
  }
}

/* Now animations work because TypeScript knows it's an angle */

Modern CSS Patterns That Replace JavaScript

Sticky Headers Without JavaScript

.header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

Scroll Snap (Replaces Carousel Libraries)

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  gap: 1rem;

  /* Hide scrollbar but keep functionality */
  scrollbar-width: none;
  &::-webkit-scrollbar { display: none; }
}

.carousel-item {
  flex: 0 0 300px;
  scroll-snap-align: start;
}

Field Validation States (No JavaScript)

input:valid {
  border-color: green;
}

input:invalid:not(:placeholder-shown) {
  border-color: red;
}

input:out-of-range {
  background: rgba(255, 0, 0, 0.1);
}

The Death of Utility-Class Bloat

Here’s the pattern I’ve seen work in 2026: Cascade Layers + Container Queries + Modern Selectors = No Tailwind Required.

Before (Tailwind-Heavy)

<div class="flex flex-col md:flex-row gap-4 p-4 md:p-6 lg:p-8 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
  <div class="w-full md:w-1/3">
    <img class="w-full h-48 md:h-full object-cover rounded-t-lg md:rounded-l-lg md:rounded-tr-none" src="..." />
  </div>
  <div class="flex-1 p-4">
    <h3 class="text-xl font-bold text-gray-900 mb-2">Title</h3>
    <p class="text-gray-600 text-sm mb-4">Content</p>
  </div>
</div>

After (Native CSS with Layers)

@layer components {
  .feature-card {
    display: grid;
    grid-template-areas:
      "image"
      "content";
    background: white;
    border-radius: 0.5rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    transition: box-shadow 0.2s;

    &:hover {
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
  }

  @container (min-width: 400px) {
    .feature-card {
      grid-template-areas: "image content";
      grid-template-columns: 1fr 2fr;
    }
  }

  .feature-card-image {
    grid-area: image;
    width: 100%;
    height: 12rem;
    object-fit: cover;

    @container (min-width: 400px) {
      height: 100%;
      border-radius: 0.5rem 0 0 0.5rem;
    }
  }

  .feature-card-content {
    grid-area: content;
    padding: 1rem;
  }
}
<div class="feature-card">
  <img class="feature-card-image" src="..." />
  <div class="feature-card-content">
    <h3>Title</h3>
    <p>Content</p>
  </div>
</div>

The CSS Stack in 2026

Modern CSS (2026):
├── Container Queries     → Component-level responsiveness
├── Cascade Layers        → Specificity control
├── :has() Selector       → Relational styling
├── @property            → Typed animations
├── @layer               → Import ordering
├── gap in flexbox       → No more margin hacks
├── subgrid              → Aligned nested grids
└── color-mix()          → Dynamic color utilities

Supporting Tools:
├── PostCSS with autoprefixer (minimal)
├── Lightning CSS (10x faster than PostCSS)
└── Vanilla Extract (type-safe CSS-in-TS, when needed)

My Recommendation

Stop reaching for Tailwind by default. For most projects, modern CSS with Cascade Layers gives you:

  • Better maintainability (readable class names)
  • Smaller bundle (no utility CSS to download)
  • No build step for basic projects
  • True cascade inheritance

Use Tailwind when:

  • You need rapid prototyping with a team unfamiliar with CSS
  • You’re building a design system with a dedicated CSS expert
  • The project genuinely needs 500+ utility classes

Use native CSS when:

  • You’re building a product that needs maintainable styles
  • Your team knows CSS
  • You want smaller bundles and no runtime dependency

The era of “CSS is too hard, let’s use a utility framework” is over. Modern CSS has caught up.

What’s your CSS stack in 2026? Have you moved back to vanilla CSS, or are you still all-in on utility frameworks?

Multi-tenant SaaS for $30/mo: wildcard SSL + nginx vhost generator + Flask routing

Multi-tenant SaaS for $30/mo: the actual architecture

Every “build a SaaS” tutorial reaches for Kubernetes, managed Postgres, and three cloud services before the first user signs up. You don’t need that. Here’s a multi-tenant setup that runs 50+ tenants on subdomains, on one $30/mo VPS.

The requirement

White-label partnership program. Each partner gets a storefront at partner-{slug}.guardlabs.online with our catalog, their referral IDs baked in. New partner → automatic provisioning after payment webhook.

Phase 1 (0-50 tenants): wildcard SSL + nginx + Flask

Wildcard SSL via Let’s Encrypt — one cert covers *.guardlabs.online:

certbot certonly --manual --preferred-challenges dns 
  -d "*.guardlabs.online" -d "guardlabs.online"

(DNS-01 challenge — add the TXT record your DNS provider, certbot validates.)

nginx server block routing by $host:

server {
    listen 443 ssl;
    server_name ~^partner-(?<partner_slug>.+).guardlabs.online$;
    ssl_certificate     /etc/letsencrypt/live/guardlabs.online/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/guardlabs.online/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8090;
        proxy_set_header Host $host;
        proxy_set_header X-Partner-Slug $partner_slug;
    }
}

Flask reads the header, scopes everything to that partner:

@app.before_request
def load_partner():
    slug = request.headers.get("X-Partner-Slug")
    if slug:
        g.partner = db.execute(
            "SELECT * FROM partners WHERE slug=?", (slug,)
        ).fetchone()
        if not g.partner:
            abort(404)

@app.route("/")
def storefront():
    products = get_catalog()  # global catalog
    return render_template("store.html",
        products=products,
        ref_id=g.partner["ref_id"],  # injected into every buy link
        partner_brand=g.partner["brand_name"])

Provisioning on webhook — when Whop sends “new subscription”:

@app.route("/webhook/whop", methods=["POST"])
def whop_webhook():
    event = verify_and_parse(request)
    if event["type"] == "subscription.created":
        slug = slugify(event["user"]["username"])
        # 1. DB row
        db.execute("INSERT INTO partners (slug, ref_id, ...) VALUES (...)")
        # 2. nginx — no per-partner config needed! Regex server_name catches it.
        # 3. Done. partner-{slug}.guardlabs.online works immediately.
    return "", 200

That’s the trick: the regex server_name means zero new nginx config per partner. Wildcard SSL means zero new certs. The DB row is the only write.

Capacity: one VPS, monolith Flask, SQLite. ~50 partners with 10 concurrent users each = 500 concurrent. A $30/mo Hetzner CCX handles that without breathing hard.

Phase 2 (50-200 tenants): per-tenant SQLite

Move from shared DB to /data/partners/{partner_id}/store.db. Flask switches connection by X-Partner-Slug. ~16 hours of refactor. SQLite holds 10k+ rows per file fine; 200 partners × 10k = 2M rows total, no problem.

Phase 3 (200-500+): PostgreSQL schema-per-tenant

Now you migrate. One Postgres, schema per partner. 40 hours. Now you might want Kubernetes. Not before.

The point

I provisioned this whole thing in about 8 hours. Total infra budget for 12 months: under $5K. The “you need microservices and managed everything” advice is for companies with funding and a platform team. For a solo founder: nginx regex + wildcard SSL + a Flask before_request hook gets you to 50 paying tenants.

Try it

The partnership it powers: 7-day free trial, no card, $29/mo. guardlabs.online/partner. And the bot the whole thing grew out of: Phantom paper-trader (384 trades, 57% win-rate, public).

Question

What’s your “we over-engineered this” story? When did you reach for Kubernetes before you needed it?

Code and full launch log in public. Following along.

📥 Free chapter — 20 no-budget growth tactics

This launch log runs on a playbook. If you want the actual tactics — Google-ecosystem hacks, trend-jacking, the HARO authority play — grab two free sections of the Blueprint. No PDF wall, no login: it opens in your browser. Real numbers, real code, no fluff.

→ guardlabs.online/free-pdf