🚀 React Hooks & Rendering — A Clear Mental Model (For My Future Self Too)

🪝What are React Hooks?
Hooks are helpers that let a function component:
remember data
react to changes

🧠 Core React Hooks (WHAT + WHY)

🔹 useState — data that affects the UI
Why it exists:
React re-runs components often. Without state, values would reset every time.
Use when:
The user should see the change.

🔹 useEffect— side work after render
Why it exists:
Some work should happen after the UI is painted, not during it.
Used for:
API calls
timers

🔹useRef — remember without re-render
Why it exists:
Sometimes React needs to remember something without updating the UI.
Two main uses:
DOM access → focus input, scroll, measure
Silent storage → timer IDs, previous values, flags

🔹 useContext — shared/global data
Why it exists:
To avoid passing props through many layers.
Used for:
logged-in user
theme

🔹 useMemo — avoid heavy recalculations
Why it exists:
React re-runs components often → slow calculations can repeat unnecessarily.
Use when:
calculation is expensive

🔹 useCallback — stable functions
Why it exists:
Functions are recreated on every render.
Used mainly when:
passing callbacks to optimized child components

🌍 Rendering Types (WHY they exist)

🔹 CSR — Client Side Rendering
What: Browser builds the UI
Best for: dashboards, internal tools

🔹 SSR — Server Side Rendering
What: Server sends ready HTML
Why: Search engines need content

🔹 SSG — Static Site Generation
What: Page built once at build time
Why: Some content rarely changes

Hope it helps someone else too

Publishing a reusable React UI package as an npm module

Publishing a reusable React UI package as an npm module is one of the easiest ways to maintain consistency across multiple apps.

Here’s a simple workflow that works well:

1) Structure your package properly
Keep a clean setup like:

src/ → components + exports
dist/ → compiled build output
index.ts → single entry for exports

2) Build it in a production-ready way
Before publishing, generate:

  • ESM build (.mjs)
  • CommonJS build (.js)
  • TypeScript types (.d.ts)

Example build command:

npm run build

3) Publish it to GitHub Packages
Setup your registry in .npmrc:

@your-scope:registry=https://npm.pkg.github.com
//https://lnkd.in/dcqNXmcg

Then publish:

npm publish

4) Use beta prereleases for safe testing**
This is the cleanest way to share updates early without breaking stable users.

npm version prerelease –preid=beta
npm publish –tag beta

✅ Stable users keep installing latest
✅ Test users install beta builds
✅ You can iterate faster without risk

This release pattern is simple, scalable, and keeps upgrades controlled.

Brave CMS – A CMS Built for Simplicity, Flexibility, and Scalability

In a world filled with bloated content management systems and overly complex admin platforms, developers often search for a solution that balances simplicity, performance, customization, and maintainability. That is where Brave CMS comes in.

Brave CMS is an open-source modern content management system built with Laravel, MySQL, and Bootstrap 5, designed for developers and businesses that want a clean, extensible foundation for building websites, blogs, and online newspapers.

Rather than trying to be everything for everyone, Brave CMS focuses on excelling at the fundamentals: content management, role-based access control, security, customization, scalability, and developer experience.

Built with Laravel, MySQL, and Bootstrap 5

One of the biggest strengths of Brave CMS is the technology stack behind it.

The project is built with Laravel, MySQL, and Bootstrap 5.

Laravel serves as the backbone of the platform, bringing with it a clean MVC architecture, elegant syntax, powerful routing, authentication systems, middleware support, and an excellent developer experience overall.

Instead of dealing with messy legacy code or outdated patterns, developers work inside a structure that feels organized and maintainable from the start.

Because Brave CMS is powered by Laravel, adding new features is easy.

For the database layer, the project uses MySQL, which remains one of the most trusted relational database systems in the industry. It is fast, stable, widely supported, and perfectly suited for content-driven applications.

On the frontend side, Bootstrap 5 provides the UI foundation. The admin panel is clean, responsive, and modern without becoming visually overwhelming. Bootstrap also makes customization easier, especially for developers who want to rapidly adapt layouts or redesign parts of the interface.

The result is a CMS that feels lightweight and modern while still giving developers the structure needed for long-term projects.

Enhanced Security Features

Security is one of the areas where Brave CMS tries to take a more modern and responsible approach.

A lot of older CMS platforms became difficult to maintain over time because they relied heavily on outdated plugins, inconsistent code quality, or insecure third-party extensions. Brave CMS avoids much of that complexity by building on Laravel’s mature ecosystem and security-focused architecture.

Laravel already provides strong protection against many common vulnerabilities, including CSRF attacks, SQL injection attempts, and insecure password storage. Brave CMS benefits directly from those protections while keeping the codebase structured and predictable.

Authentication and route protection are handled through Laravel’s middleware system, which makes access control cleaner and easier to manage. Validation and sanitization are also built into the workflow, helping reduce the chances of malformed or malicious input becoming a problem.

But security is not only about technical protection.

A secure application is also one that developers can actually understand and maintain. Overly complicated systems often become insecure simply because nobody wants to touch them anymore.

Brave CMS keeps things organized and transparent, which makes long-term maintenance significantly easier.

Easy to Customize

Customization is one of the most important qualities of a modern CMS.

Many platforms promise flexibility but eventually trap developers inside rigid architectures or endless plugin dependencies. Brave CMS takes a different approach by giving developers a clean foundation that can evolve naturally depending on project requirements.

Since the platform is built on Laravel, developers can easily extend almost every part of the system. Whether it is creating new routes, building custom admin functionality, integrating external APIs, adding new content structures, or modifying frontend templates, the architecture stays approachable and easy to work with.

This flexibility makes Brave CMS suitable for far more than simple blogs.

It can serve as the base for company websites, content platforms, admin dashboards, publishing systems, or fully customized web applications.

The Bootstrap 5 frontend also helps significantly when it comes to UI customization. Developers can quickly adapt layouts, components, spacing, colors, and responsiveness without needing to redesign everything from scratch.

Instead of locking developers into a rigid ecosystem, Brave CMS gives them room to build.

Designed to Scale

A good CMS should not only work for small projects.

It should also be capable of growing alongside the application.

Brave CMS was designed with scalability in mind, largely because it inherits many of Laravel’s strengths. The framework already supports caching systems, queues, modular application structures, API development, and performance optimization tools.

That means developers can start with a relatively small project and gradually expand functionality without needing to rebuild the platform later.

As traffic and complexity increase, Brave CMS can integrate with tools like Redis caching, queue workers, CDN services, and cloud-based hosting environments. The clean separation between backend logic, frontend rendering, and database structure also makes maintenance easier as projects grow.

Scalability is not only about handling more users.

It is also about keeping the codebase manageable over time.

One of the biggest frustrations developers face with older CMS platforms is technical debt caused by years of patchwork plugins and inconsistent architecture. Brave CMS avoids much of that by embracing modern Laravel development principles from the beginning.

Theme Support

Another important feature of Brave CMS is theme support.

If you are a UI designer/developer looking for a way to turn your HTML templates into database-powered websites, Brave CMS is perfect for you.

Instead of rebuilding the frontend structure every time, developers can create reusable themes and adapt them for different purposes.

The active theme can be easily set from the admin panel’s Settings section. That means that making a new theme and using it has zero downtime.

Comprehensive Documentation and Guides

Even the best software becomes frustrating if the documentation is poor.

One of the practical strengths of Brave CMS is that it includes a comprehensive guide designed to help developers understand installation, configuration, customization, and theme development.

Instead of spending hours digging through outdated forum posts or incomplete tutorials, developers can follow a more structured workflow and understand how the platform is intended to work.

This is particularly valuable for developers who are newer to Laravel or transitioning from older CMS ecosystems.

Clear documentation lowers friction, reduces setup time, and makes customization far less intimidating.

For long-term projects, that matters just as much as the code itself.

Final Thoughts

Brave CMS represents a modern approach to content management.

Instead of overwhelming developers with unnecessary complexity, it focuses on clean architecture, strong foundations, customization, scalability, and practical usability.

Built with Laravel, MySQL, and Bootstrap 5, the platform combines modern backend engineering with responsive frontend design.

Its enhanced security structure helps developers build safer applications, while its flexible architecture allows projects to evolve naturally over time.

The addition of theme support and comprehensive documentation further strengthens its position as a developer-friendly CMS solution.

For developers looking for a Laravel-powered CMS that is clean, scalable, customizable, and grounded in modern development practices, Brave CMS is a compelling option worth exploring.

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?