Stop Tab-Switching: A Developer’s Guide to Color Tools That Actually Fit the Workflow

Color palette generator ilovehue.co

Every developer building UI eventually hits the same problem: you’re constantly switching tabs for color tools—contrast checkers, palette generators, Tailwind scales, image pickers.
It should take 30 seconds. It takes 15 minutes.

Color work is one of those tasks that should take thirty seconds but ends up consuming fifteen minutes of context-switching. This guide covers the color tools that are actually worth bookmarking — what they’re good for, where they fall short, and how to structure a workflow that keeps you in flow.

The Core Problem With How Most Developers Handle Color

Color tools are abundant. The problem is they’re fragmented. Most tools do one thing well: a contrast checker that only checks contrast, a palette generator that only generates palettes, a converter that only converts. So developers end up building a collection of five or six different bookmarks, each for a separate micro-task.

There are a few ways to solve this:

  1. Use multiple specialized tools and accept the context-switching cost
  2. Use a design tool like Figma which has color built in — but only when you’re already in Figma
  3. Use an integrated color suite that consolidates the common tasks in one place
    For pure development work (outside of a design tool), option 3 is increasingly viable and underused. Let’s walk through the actual tasks and what handles them well.

Task 1: Generating a Palette From a Base Color

Color palette generator ilovehue.co
This is the starting point for most UI color work. You have a brand color — a primary blue, a signature green — and you need to build a usable palette from it.

The naive approach is to eyeball lighter and darker variants. The smarter approach is to use color theory: complementary, analogous, triadic, and split-complementary relationships to find colors that work together perceptually, not just visually.

What to look for in a palette generator:

  • Color theory engine (not just random generation)
  • Multiple harmony rules (complementary, triadic, analogous, split-complementary)
  • Exportable output in HEX, RGB, and CSS formats
  • Ability to lock individual colors while regenerating the rest
    I Love Hue’s palette generator uses perceptual blending with color theory rules to generate five-color palettes from a single HEX input. The spacebar randomizes within the same harmony rule, which is useful for exploring variations without starting from scratch. Output comes in CSS, Tailwind config format, and JSON.

Task 2: Building a Tailwind Color Scale

If you use Tailwind CSS, you’ve probably run into the need for a custom color scale — the 50 through 950 range that Tailwind’s default palette follows. Creating this manually is tedious and the results are often perceptually uneven (the jumps between steps don’t feel consistent to the eye).

The proper way to generate a Tailwind scale is to work in a perceptually uniform color space (like OKLCH or HSLuv) rather than just interpolating linearly in RGB or HSL. Linear RGB interpolation produces scales where the middle steps feel muddy.

A good Tailwind generator should:

  • Output all 11 steps (50, 100, 200 … 900, 950)
  • Format output directly for tailwind.config.js — ready to paste
  • Use perceptually uniform spacing between steps
  • Allow a custom name for the color token
    Tailwind Color Generator takes any HEX input and outputs a complete named 11-step scale formatted for direct use in your config file. For developers who live in Tailwind, this alone is worth bookmarking.

Task 3: Extracting Colors From an Image or Screenshot

Image color extractor ilovehue.co

Brand work, competitor research, and design inspiration all involve looking at an image and wanting to know the exact color values. Whether it’s a screenshot of a website, a photo, or a logo file, the workflow is the same: upload, identify, extract.

The frustrating version of this workflow is using a desktop color picker that requires you to have the image open locally, or a browser extension that only works on live web pages. A browser-based image color extractor that works on any uploaded file is more flexible.

Image Color Extractor auto detects dominant colors, user can increase the color swatch to eight selections and it also includes a magnifier tool for pixel-precise color sampling. Upload any image, hover over the exact area you want to sample, and get HEX, RGB, and HSL values. Useful for extracting brand colors from logos or building palettes from photography.

Task 4: Checking WCAG Contrast

Contrast Checker ilovehue.co

This one is non-negotiable for production work. WCAG 2.1 Level AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. Level AAA requires 7:1. Getting this wrong isn’t just a design issue — it’s an accessibility failure that affects users with low vision, and increasingly a legal consideration.

The two things a contrast checker needs to do well:

  1. Real-time feedback as you adjust colors (not a submit-and-wait flow)
  2. Clear pass/fail indication for both AA and AAA, for both normal and large text
    Most standalone contrast checkers do this adequately. The advantage of having it inside a broader tool is that you can check contrast against a palette you just generated without copying values between tabs.

Contrast Checker shows the ratio, and AA/AAA pass/fail status for text and UI component contrast simultaneously. You can also reach it directly at ilovehue.co/contrast-checker?color=YOUR_BG&fg=YOUR_TEXT which makes it easy to bookmark specific color combinations for recurring checks.

Task 5: Exploring Named Colors and Finding Exact HEX Values

Color Directory ilovehue.co
Sometimes you know the name of a color — “teal,” “indigo,” “chartreuse” — but you need its precise HEX value and its RGB/CMYK equivalents. Color naming is notoriously inconsistent across tools and platforms; “teal” in CSS is #008080, but “teal” in Pantone, paint, and fashion are all different things.

A proper color directory gives you:

  • Named colors with verified technical values
  • HEX, RGB, HSL, and CMYK for each entry
  • Nearby and related colors for discovery
  • Color psychology and typical use context
    I Love Hue’s Color Directory has 5,680+ named colors with complete technical data. It’s useful both for lookup (“what exactly is Prussian Blue?”) and for discovery browsing when you’re looking for color inspiration.

Task 6: Building Shades and Tints for a Design System

Shades and tints generator ilovehue.co

Once you have your primary colors established, building a complete design system requires tints (lighter versions) and shades (darker versions) for states, hover effects, disabled states, and background fills.

The standard approach is to generate a 10-step tint and shade range from your base color. The tool should:

  • Show tints, tones, and shades separately (these are different things — tints add white, shades add black, tones add gray)
  • Generate at consistent percentage steps
  • Output HEX values for each step
    Shade Generator generates 5, 10 and 20-step tint, tone, and shade ranges from any base color. Combined with the Tailwind generator, this covers most of what you need to build a complete color system for a design token library.

Task 7: Color Conversions

Color Converter ilovehue.co
HEX to RGB, RGB to HSL, HSL to CMYK — this comes up constantly when working across different tools, design files, and print/digital handoffs. Browser DevTools handles some of this but not all format combinations.

A color converter worth using handles: HEX ↔ RGB ↔ HSL ↔ CMYK ↔ HSV, with real-time preview and copy-to-clipboard for each format.

Color Converter covers all major formats with instant conversion and copy buttons. Nothing revolutionary, but having it in the same place as your other color tools removes one more reason to open a separate tab.

Putting It Together: A Practical Color Workflow

Here’s how these tools fit into an actual development workflow, from brand color to production-ready design system:

Step 1 — Extract brand colors from a logo or reference image using the Image Color Extractor. Get exact HEX values.

Step 2 — Build your palette using the Palette Generator with your primary brand color as the base. Try complementary or split-complementary harmony rules for accent colors.

Step 3 — Generate Tailwind scales for your primary, secondary, and accent colors using the Tailwind Generator. Paste directly into tailwind.config.js.

Step 4 — Generate shades and tints for each key color using the Shade Generator. These become your semantic tokens (–color-primary-100 through –color-primary-900).

Step 5 — Check accessibility by running your text/background combinations through the Contrast Checker. Fix any AA failures before they reach code review.

Step 6 — Document by saving HEX values with their names. The Color Directory helps identify standard names for colors you want to reference consistently in design documentation.

The Tab-Switching Problem, Solved

The tools above aren’t magic — any individual tool in this list has a specialist alternative that might do that one task slightly better. The value is in consolidation. When your palette generator, Tailwind scale builder, contrast checker, image extractor, shade generator, and color converter are all in the same workspace, you spend more time making decisions and less time navigating between applications.

For most development color work, I Love Hue covers the full workflow in one place. It’s free, requires no account, and works entirely in the browser.

Built something with these tools? Have a color workflow that works better for you? Drop it in the comments — always looking for better approaches.

DevOps vs MLOps vs AIOps: What Changes, What Stays, and a Simple Roadmap to Get Started

A lot of teams throw around DevOps, MLOps, and AIOps like they are the same thing with slightly different branding.

They are not.

They overlap, but each one solves a different operational problem:

  • DevOps helps teams ship software faster and more reliably.
  • MLOps helps teams build, deploy, monitor, and retrain machine learning systems.
  • AIOps helps IT and platform teams detect, correlate, predict, and resolve operational issues using AI.

If you mix them up, you usually end up buying the wrong tools or starting at the wrong layer.

The short version

Think about them like this:

  • DevOps is about the software delivery system.
  • MLOps is about the machine learning lifecycle.
  • AIOps is about operating complex production systems with smarter monitoring and automation.

Here is the simplest mental model:

DevOps vs MLOps vs AIOps diagram

What DevOps actually is

Microsoft describes DevOps as the union of people, process, and products to enable continuous delivery of value.

In plain words, DevOps is the operating model that helps engineering teams:

  • collaborate instead of throwing work over the wall
  • automate builds, tests, and deployments
  • ship changes in smaller batches
  • recover faster when something breaks
  • use feedback from production to improve the next release

Typical DevOps building blocks:

  • Git-based version control
  • CI/CD pipelines
  • infrastructure as code
  • automated testing
  • observability and incident response
  • shared ownership between dev and ops

If your team mainly ships web apps, APIs, mobile backends, or internal tools, DevOps is the foundation.

What MLOps adds on top of DevOps

MLOps starts where normal software delivery stops being enough.

A machine learning system is not just code. It also depends on:

  • training data
  • feature pipelines
  • experiments
  • model artifacts
  • model registry and lineage
  • model validation
  • drift monitoring
  • retraining workflows

That is why Microsoft and Google both frame MLOps as DevOps adapted for machine learning.

A normal backend service usually changes when code changes.
An ML system can fail even when the code did not change at all.

Why? Because:

  • the incoming data changed
  • the feature distribution shifted
  • the model got stale
  • online behavior drifted away from training assumptions

That is the extra headache MLOps is built for.

Typical MLOps building blocks:

  • experiment tracking
  • dataset and feature versioning
  • reproducible training pipelines
  • model registry
  • offline and online evaluation
  • deployment strategies for models
  • monitoring for drift, quality, and latency
  • retraining and governance workflows

What AIOps is really for

AIOps is usually the most misunderstood one.

It is not “using AI in your product.”
It is not the same thing as training models.
It is not just another word for observability.

AIOps is about using AI and machine learning to improve IT operations.

That usually means working across things like:

  • logs
  • metrics
  • traces
  • alerts
  • incidents
  • topology or dependency signals
  • service desk or ITSM data

The goal is to help ops and platform teams do things like:

  • reduce alert noise
  • correlate related incidents
  • detect anomalies earlier
  • speed up root cause analysis
  • predict outages or capacity issues
  • automate common remediation steps

If DevOps asks, “How do we ship software better?” then AIOps asks, “How do we operate a messy, noisy production environment without drowning?”

Where people get confused

The confusion usually happens because all three involve automation, monitoring, and feedback loops.

That overlap is real, but the center of gravity is different.

DevOps centers on software delivery

Main question:

  • How do we build, test, release, and operate application code reliably?

MLOps centers on model lifecycle management

Main question:

  • How do we train, deploy, monitor, govern, and refresh ML models reliably?

AIOps centers on operational intelligence

Main question:

  • How do we make sense of huge operational signal streams and reduce firefighting?

A practical comparison

Area DevOps MLOps AIOps
Core focus Software delivery ML lifecycle IT operations intelligence
Primary asset Application code Models, data, pipelines Operational telemetry
Main users Software engineers, platform teams Data scientists, ML engineers, platform teams SRE, platform, ops, IT teams
Typical pipeline Build, test, deploy Train, validate, register, deploy, monitor, retrain Ingest, correlate, detect, predict, remediate
Failure mode Broken deploys, flaky releases, config drift Model drift, stale features, bad data, weak reproducibility Alert storms, noisy incidents, slow RCA
Key metric examples Lead time, deploy frequency, MTTR Model accuracy, drift, inference latency, retrain cadence MTTD, MTTR, false positive reduction, incident correlation quality

The relationship in one diagram

This is the part most teams actually need to internalize:

  • DevOps is the base delivery discipline.
  • MLOps extends that base for ML systems.
  • AIOps helps operate increasingly complex environments.

How DevOps, MLOps, and AIOps relate

When you need DevOps only

You probably need only DevOps if:

  • you are shipping standard software products
  • there are no ML models in production
  • your biggest pain is release speed, reliability, testing, or environment consistency
  • your monitoring stack is still manageable by humans

This is where a lot of startups and early product teams should stay for a while.

When you need MLOps

You need MLOps when:

  • models are part of the product or decision flow
  • training is repeated, not one-off
  • multiple people work on experiments and deployments
  • you need traceability for which data and code produced a model
  • you care about drift, retraining, approvals, or governance

If your ML work still lives in notebooks and manual handoffs, MLOps is probably overdue.

When you need AIOps

You need AIOps when:

  • your environment generates too many alerts for humans to triage well
  • incident response is noisy and slow
  • you run many services, clusters, tools, and dependencies
  • correlation across systems is painful
  • you want smarter anomaly detection or automated remediation

If your production setup is still small, buying an AIOps platform too early is usually overkill.

What most teams should do first

This is the part that saves people from making a bad call.

If your CI/CD is shaky, your testing is weak, and your production visibility is already messy, do not jump straight to AIOps.

That is the classic shiny-object move.

You will just add another layer of complexity on top of a weak foundation.

The usual order should be:

  1. Get DevOps basics solid
  2. Add MLOps if you run ML in production
  3. Add AIOps when operational complexity is genuinely large enough

A realistic roadmap to get started

Stage 1: Start with DevOps fundamentals

Get these working first:

  • source control discipline
  • automated builds and tests
  • CI/CD pipelines
  • infrastructure as code
  • environment parity
  • basic logs, metrics, and alerts
  • on-call and incident habits

Good outcome:

  • shipping becomes predictable
  • rollback is easier
  • production changes are less scary

Stage 2: Add platform reliability and observability maturity

Before jumping into AIOps, tighten:

  • service ownership
  • dashboards that people actually use
  • alert quality
  • runbooks
  • deployment visibility
  • dependency mapping
  • incident reviews with action items

Good outcome:

  • you have signal worth automating
  • your monitoring is not just noise

Stage 3: Add MLOps if ML is part of the business

Bring in:

  • experiment tracking
  • model and dataset versioning
  • reproducible training
  • model validation gates
  • registry and approval flow
  • drift and inference monitoring
  • retraining triggers

Good outcome:

  • models stop being notebook magic and start becoming real production assets

Stage 4: Add AIOps when complexity earns it

Only do this when you already have enough telemetry and incident volume.

Focus on:

  • anomaly detection
  • alert deduplication and correlation
  • topology-aware incident grouping
  • root cause assistance
  • predictive scaling or outage signals
  • safe auto-remediation for known cases

Good outcome:

  • fewer useless alerts
  • faster response
  • less human toil during incidents

A simple stack view

If you want one clean picture, it looks like this:

DevOps  -> build and ship software reliably
MLOps   -> build and operate ML systems reliably
AIOps   -> operate large IT systems more intelligently

That is the separation that matters.

Final takeaway

Here is the easiest way to remember it:

  • DevOps makes software delivery reliable.
  • MLOps makes machine learning delivery reliable.
  • AIOps makes operations smarter at scale.

Start with the problem you actually have.

If you do not run ML in production, you probably do not need MLOps yet.
If your operational noise is still manageable, you probably do not need AIOps yet.
If your release process is still shaky, DevOps is still the main job.

That is not boring advice.
That is the advice that saves teams months.

References

  1. Microsoft Learn, What is DevOps?
    https://learn.microsoft.com/en-us/devops/what-is-devops
  2. Microsoft Learn, What is DevOps? (training module)
    https://learn.microsoft.com/en-us/training/modules/get-started-with-devops/2-what-is-devops
  3. Microsoft Azure, Machine learning operations (MLOps)
    https://azure.microsoft.com/en-us/products/machine-learning/mlops/
  4. Microsoft Learn, MLOps model management with Azure Machine Learning
    https://learn.microsoft.com/en-us/azure/machine-learning/concept-model-management-and-deployment?view=azureml-api-2
  5. Google Cloud, What is MLOps?
    https://cloud.google.com/discover/what-is-mlops
  6. IBM, AIOps vs. MLOps: Harnessing big data for “smarter” ITOPs
    https://www.ibm.com/think/topics/aiops-vs-mlops
  7. IBM, What is observability in AIOps?
    https://www.ibm.com/think/topics/aiops-observability

Run Powerful AI Coding Locally on a Normal Laptop

Run Powerful AI Coding Locally on a Normal Laptop
A Developer-Friendly Guide to Setting Up ROO Code + Ollama + Qwen (8GB/16GB RAM)

If you are a developer who wants to use AI coding assistants locally without paying for cloud APIs or owning a high-end GPU, this guide is for you.

In this article, we will set up:

ROO Code inside Visual Studio Code
Ollama for running local AI models
Qwen2.5-Coder model locally
Optimized for:
8GB RAM laptops
16GB RAM laptops
No dedicated GPU / No VRAM

By the end, you’ll have your own private AI coding assistant running fully offline.
Why Run AI Locally?

Running AI locally gives developers:

✅ No API cost
✅ Better privacy
✅ Faster experimentation
✅ Offline development
✅ Full control over models
✅ No dependency on cloud providers

Recommended Hardware
Configuration Recommended Model
8GB RAM qwen2.5-coder:1.5b
16GB RAM qwen2.5-coder:7b
16GB+ RAM qwen2.5-coder:14b (slow but possible)

If you have no GPU, don’t worry. Ollama can run models entirely on CPU.

Step 1 — Install Visual Studio Code

  1. Download and install:
  2. Visual Studio Code
  3. Use the official website:

After installation:

code --version

Verify VS Code is properly installed.

Step 2 — Install Ollama

Install:

Ollama

Windows

Download installer from the official Ollama website.

Verify installation:

ollama --version

Step 3 — Start Ollama

Run:

ollama serve

This starts the local AI server at:

http://localhost:11434

Keep this terminal running.

Step 4 — Install Qwen Coding Model
For 8GB RAM Systems

Recommended:

ollama run qwen2.5-coder:1.5b
Why?

  1. Lightweight
  2. Fast on CPU
  3. Good enough for:
  4. Code generation
  5. Refactoring
  6. Unit tests
  7. Small automation tasks

For 16GB RAM Systems

Recommended:
ollama run qwen2.5-coder:7b

This gives much better:

  1. Reasoning
  2. Architecture suggestions
  3. Refactoring quality
  4. Multi-file understanding

Step 5 — Test the Model

Try:

ollama run qwen2.5-coder:7b

Then ask:

Who are you and create a hello world example in python

If the model responds, you’re ready.

Step 6 — Install ROO Code Extension

Inside VS Code:

Open Extensions
Search:

Roo Code
Install the extension

ROO Code converts VS Code into an AI-powered development environment.

Step 7 — Configure ROO Code for Ollama

Open ROO Code settings.

Set:

Provider: Ollama

API Endpoint:

http://localhost:11434

Model:

For 8GB RAM:

qwen2.5-coder:1.5b

For 16GB RAM:

qwen2.5-coder:7b

Save settings.

Step 8 — First AI Coding Test

Open a project and ask ROO Code:

Create a Java Spring Boot CRUD API with Controller, Service, Repository

Or:

Generate Cypress automation for login page

You now have a local AI coding assistant.

Best Practices for Low-RAM Systems
For 8GB RAM Machines
Recommended Settings
Setting Value
Context Window Small
Concurrent Apps Minimal
Model 1.5B
Browser Tabs Limited
Avoid

❌ Running Docker + AI together
❌ Opening large IDE projects
❌ Using 7B models continuously

Best Practices for 16GB RAM Machines

You can comfortably use:

qwen2.5-coder:7b
Medium-size repositories
Spring Boot projects
React applications
Cypress automation generation

Recommended:

OLLAMA_NUM_PARALLEL=1

This prevents RAM spikes.

Performance Optimization Tips
Reduce Model Temperature

Better coding consistency:

temperature = 0.2
Keep Context Smaller

Instead of entire repositories:

✅ Open only relevant folders

This improves response quality and speed.

Restart Ollama Occasionally

Long sessions can consume memory.

Restart:

ollama stop
ollama serve
Recommended Models by Use Case
Use Case Recommended Model
Basic coding qwen2.5-coder:1.5b
Java development qwen2.5-coder:7b
Test automation qwen2.5-coder:7b
Architecture discussion qwen2.5-coder:7b
Large enterprise code DeepSeek-Coder 14B (16GB+)
What Works Surprisingly Well Locally?

Even without a GPU, local models perform very well for:

✅ Boilerplate generation
✅ Refactoring
✅ Unit tests
✅ Cypress automation
✅ SQL generation
✅ Spring Boot scaffolding
✅ API creation
✅ Debugging suggestions
✅ Documentation generation

Limitations

Be realistic about CPU-only setups.

You may experience:

Slower response time
Limited context handling
Occasional hallucinations
Reduced multi-file reasoning

But for day-to-day development, the experience is still highly productive.

My Recommended Setup
For Most Developers
8GB RAM
Ollama + qwen2.5-coder:1.5b + Roo Code
16GB RAM
Ollama + qwen2.5-coder:7b + Roo Code

This provides the best balance between:

Performance
Memory usage
Coding quality
Stability
Final Thoughts

Local AI development is no longer limited to expensive GPUs.

Today, even a normal laptop can run surprisingly capable coding assistants using:

Ollama
Qwen2.5-Coder
Visual Studio Code
ROO Code

For developers working in Java, Spring Boot, React, Cypress, AI automation, and system design — this setup is an excellent starting point into the world of local AI engineering.

Useful Commands Cheat Sheet

Start Ollama

ollama serve

Run 1.5B model

ollama run qwen2.5-coder:1.5b

Run 7B model

ollama run qwen2.5-coder:7b

List installed models

ollama list

Remove model

ollama rm qwen2.5-coder:7b
Tags

5 n8n Automations Every WooCommerce Store Needs (Save 10+ Hours/Week)

5 n8n Automations Every WooCommerce Store Needs (Save 10+ Hours/Week)

Running a WooCommerce store means drowning in repetitive tasks: checking inventory, logging orders to spreadsheets, chasing abandoned carts, sending follow-up emails, compiling daily sales reports. Most store owners spend hours each week on work that should be automatic.

This guide shows you 5 n8n workflows that eliminate the most time-consuming WooCommerce admin tasks — with full workflow JSON you can import and use today.

Setup: Connect WooCommerce to n8n (5 minutes)

  1. In your WordPress admin, go to WooCommerce → Settings → Advanced → REST API
  2. Click Add key → set description “n8n”, user (admin), permissions Read/Write
  3. Copy the Consumer Key and Consumer Secret
  4. In n8n, create a WooCommerce credential: add your store URL + those keys

That’s it. All 5 workflows below use this credential.

Workflow 1: Low Stock Alert → Slack + Email

The problem: You run out of stock without knowing, losing sales. WooCommerce’s built-in alerts are easy to miss.

What this does: Every hour, checks all products with stock below your threshold and sends a Slack message + email listing which products need restocking — with current stock count and a direct admin link.

Workflow (6 nodes):

{
  "name": "WooCommerce Low Stock Alert",
  "nodes": [
    {
      "name": "Every Hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": { "interval": [{ "field": "hours", "hoursInterval": 1 }] }
      },
      "position": [250, 300]
    },
    {
      "name": "Get Products",
      "type": "n8n-nodes-base.wooCommerce",
      "parameters": {
        "resource": "product",
        "operation": "getAll",
        "returnAll": true,
        "filters": {
          "stock_status": "instock",
          "manage_stock": true
        }
      },
      "position": [450, 300]
    },
    {
      "name": "Filter Low Stock",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const LOW_STOCK_THRESHOLD = 5;nconst lowStock = items.filter(item => {n  const stock = item.json.stock_quantity;n  return stock !== null && stock <= LOW_STOCK_THRESHOLD;n});nif (lowStock.length === 0) return [];nconst report = lowStock.map(p =>n  `• ${p.json.name} — ${p.json.stock_quantity} left (${process.env.WP_URL}/wp-admin/post.php?post=${p.json.id}&action=edit)`n).join('\n');nreturn [{ json: { report, count: lowStock.length } }];"
      },
      "position": [650, 300]
    },
    {
      "name": "IF Has Low Stock",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "number": [{ "value1": "={{$json.count}}", "operation": "larger", "value2": 0 }]
        }
      },
      "position": [850, 300]
    },
    {
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#inventory-alerts",
        "text": "⚠️ Low stock alert ({{$json.count}} products):nn{{$json.report}}"
      },
      "position": [1050, 200]
    },
    {
      "name": "Email Alert",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "toEmail": "[email protected]",
        "subject": "⚠️ WooCommerce Low Stock Alert — {{$json.count}} products",
        "message": "The following products are running low:nn{{$json.report}}"
      },
      "position": [1050, 400]
    }
  ]
}

Customise: Change LOW_STOCK_THRESHOLD to any number. Add a Google Sheets log node to track restock history.

Workflow 2: New Order → Google Sheets Log + Slack Notification

The problem: Your order data lives in WooCommerce only. You can’t easily analyse trends, share data with your team, or build custom reports without exporting manually.

What this does: Every new WooCommerce order automatically gets logged to a Google Sheets master order table AND posts a Slack notification — so your team knows about every sale in real time.

Workflow (4 nodes):

{
  "name": "WooCommerce Order Logger",
  "nodes": [
    {
      "name": "WooCommerce Webhook",
      "type": "n8n-nodes-base.wooCommerceTrigger",
      "parameters": {
        "event": "order.created"
      },
      "position": [250, 300]
    },
    {
      "name": "Format Order",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const order = items[0].json;nconst customer = `${order.billing.first_name} ${order.billing.last_name}`;nconst items_list = order.line_items.map(i => `${i.name} x${i.quantity}`).join(', ');nreturn [{n  json: {n    order_id: order.id,n    date: order.date_created,n    customer,n    email: order.billing.email,n    total: order.total,n    items: items_list,n    status: order.status,n    payment: order.payment_method_titlen  }n}];"
      },
      "position": [450, 300]
    },
    {
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Orders!A:I",
        "values": {
          "Order ID": "={{$json.order_id}}",
          "Date": "={{$json.date}}",
          "Customer": "={{$json.customer}}",
          "Email": "={{$json.email}}",
          "Total": "={{$json.total}}",
          "Items": "={{$json.items}}",
          "Status": "={{$json.status}}",
          "Payment": "={{$json.payment}}"
        }
      },
      "position": [650, 200]
    },
    {
      "name": "Slack Notification",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#sales",
        "text": "🛒 New order #{{$json.order_id}} — {{$json.customer}} — ${{$json.total}}n{{$json.items}}"
      },
      "position": [650, 400]
    }
  ]
}

Customise: Replace the WooCommerce Trigger with a Schedule Trigger that polls every 15 min if webhooks are hard to set up on your host. Add a Discord node instead of Slack.

Workflow 3: Abandoned Cart Recovery Email

The problem: 70%+ of WooCommerce carts are abandoned. The built-in recovery email (only in WooCommerce Pro) costs $199/year. n8n does the same thing free.

What this does: Every 30 minutes, checks for carts abandoned in the last 1 hour and sends a personalised recovery email with the exact items left behind and a direct checkout link.

Workflow (5 nodes):

{
  "name": "Abandoned Cart Recovery",
  "nodes": [
    {
      "name": "Every 30 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": { "interval": [{ "field": "minutes", "minutesInterval": 30 }] }
      },
      "position": [250, 300]
    },
    {
      "name": "Get Pending Orders (1h)",
      "type": "n8n-nodes-base.wooCommerce",
      "parameters": {
        "resource": "order",
        "operation": "getAll",
        "returnAll": false,
        "limit": 50,
        "filters": {
          "status": "pending",
          "after": "={{ DateTime.now().minus({hours:2}).toISO() }}",
          "before": "={{ DateTime.now().minus({hours:1}).toISO() }}"
        }
      },
      "position": [450, 300]
    },
    {
      "name": "Build Recovery Email",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "return items.map(item => {n  const order = item.json;n  const cartItems = order.line_items.map(i =>n    `<li>${i.name} x${i.quantity} — $${i.total}</li>`n  ).join('');n  const html = `<p>Hi ${order.billing.first_name},</p>n<p>You left some great items in your cart:</p>n<ul>${cartItems}</ul>n<p><strong>Total: $${order.total}</strong></p>n<p><a href="${order.checkout_payment_url}">Complete your order →</a></p>`;n  return { json: { email: order.billing.email, name: order.billing.first_name, html, order_id: order.id } };n});"
      },
      "position": [650, 300]
    },
    {
      "name": "IF Has Email",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "string": [{ "value1": "={{$json.email}}", "operation": "isNotEmpty" }]
        }
      },
      "position": [850, 300]
    },
    {
      "name": "Send Recovery Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "toEmail": "={{$json.email}}",
        "subject": "Hey {{$json.name}}, you left something behind...",
        "message": "={{$json.html}}",
        "options": { "emailType": "html" }
      },
      "position": [1050, 300]
    }
  ]
}

Note: WooCommerce creates an order in “pending” status when a customer starts checkout and doesn’t complete it. This workflow targets those orders. Check your host’s cron scheduling for the WooCommerce sessions plugin if you want to catch earlier-stage abandonment.

Workflow 4: AI Customer Review Tagger

The problem: Reading every customer review manually to understand what’s working and what isn’t takes hours. You need a way to automatically categorise feedback.

What this does: When a new WooCommerce review is submitted, Claude AI reads it and tags it as Positive/Negative/Feature Request — then logs it to Google Sheets with the AI’s 1-line summary. You get a searchable database of every piece of customer feedback, automatically categorised.

Workflow (4 nodes):

{
  "name": "WooCommerce AI Review Tagger",
  "nodes": [
    {
      "name": "WooCommerce Review Webhook",
      "type": "n8n-nodes-base.wooCommerceTrigger",
      "parameters": {
        "event": "review.created"
      },
      "position": [250, 300]
    },
    {
      "name": "Claude AI Tagger",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "headers": {
          "x-api-key": "YOUR_CLAUDE_API_KEY",
          "anthropic-version": "2023-06-01",
          "content-type": "application/json"
        },
        "body": {
          "model": "claude-haiku-4-5-20251001",
          "max_tokens": 100,
          "messages": [{
            "role": "user",
            "content": "Categorise this WooCommerce review. Reply with JSON only: {tag: 'Positive'|'Negative'|'Feature Request', summary: '10 words max'}nnReview: {{$json.review}}"
          }]
        }
      },
      "position": [450, 300]
    },
    {
      "name": "Parse AI Response",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const text = items[0].json.content[0].text;nconst parsed = JSON.parse(text);nreturn [{ json: { ...parsed, review: $('WooCommerce Review Webhook').first().json.review, product: $('WooCommerce Review Webhook').first().json.product_id, rating: $('WooCommerce Review Webhook').first().json.rating } }];"
      },
      "position": [650, 300]
    },
    {
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Reviews!A:E",
        "values": {
          "Product ID": "={{$json.product}}",
          "Rating": "={{$json.rating}}",
          "Tag": "={{$json.tag}}",
          "AI Summary": "={{$json.summary}}",
          "Review": "={{$json.review}}"
        }
      },
      "position": [850, 300]
    }
  ]
}

Customise: Swap Claude Haiku for OpenAI GPT-4o-mini to cut cost further. Add a Slack alert when a “Negative” tag is detected so you can respond quickly.

Workflow 5: Daily WooCommerce Sales Report

The problem: Checking WooCommerce stats every morning means logging in, waiting for the dashboard to load, and mentally compiling numbers. Total waste of 10 minutes every day.

What this does: Every morning at 7 AM, this workflow pulls the previous day’s orders, calculates revenue, order count, AOV, top product, and week-over-week change — then emails you a clean summary and posts it to Slack.

Workflow (5 nodes):

{
  "name": "WooCommerce Daily Sales Report",
  "nodes": [
    {
      "name": "7 AM Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": { "interval": [{ "field": "cronExpression", "expression": "0 7 * * *" }] }
      },
      "position": [250, 300]
    },
    {
      "name": "Get Yesterday's Orders",
      "type": "n8n-nodes-base.wooCommerce",
      "parameters": {
        "resource": "order",
        "operation": "getAll",
        "returnAll": true,
        "filters": {
          "status": "completed,processing",
          "after": "={{ DateTime.now().minus({days:1}).startOf('day').toISO() }}",
          "before": "={{ DateTime.now().startOf('day').toISO() }}"
        }
      },
      "position": [450, 300]
    },
    {
      "name": "Calculate Stats",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const orders = items.map(i => i.json);nif (orders.length === 0) return [{ json: { revenue: 0, orders: 0, aov: 0, top_product: 'N/A', date: new Date().toDateString() } }];nconst revenue = orders.reduce((s, o) => s + parseFloat(o.total), 0);nconst productCount = {};norders.forEach(o => o.line_items.forEach(item => {n  productCount[item.name] = (productCount[item.name] || 0) + item.quantity;n}));nconst top_product = Object.entries(productCount).sort((a,b) => b[1]-a[1])[0]?.[0] || 'N/A';nreturn [{ json: {n  date: new Date().toDateString(),n  revenue: revenue.toFixed(2),n  orders: orders.length,n  aov: (revenue / orders.length).toFixed(2),n  top_productn}}];"
      },
      "position": [650, 300]
    },
    {
      "name": "Email Report",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "toEmail": "[email protected]",
        "subject": "📊 WooCommerce Daily Report — {{$json.date}}",
        "message": "Yesterday's numbers:nn💰 Revenue: ${{$json.revenue}}n🛒 Orders: {{$json.orders}}n📈 AOV: ${{$json.aov}}n🏆 Top Product: {{$json.top_product}}",
        "options": {}
      },
      "position": [850, 200]
    },
    {
      "name": "Slack Report",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#sales",
        "text": "📊 *Yesterday's WooCommerce Report* ({{$json.date}})n💰 Revenue: ${{$json.revenue}} | 🛒 Orders: {{$json.orders}} | 📈 AOV: ${{$json.aov}}n🏆 Top product: {{$json.top_product}}"
      },
      "position": [850, 400]
    }
  ]
}

Customise: Add a Sheets log to track trends over time. Add week-over-week comparison by also fetching orders from 7 days ago. Add top 3 products instead of just 1.

The Real Cost of Manual Store Management

Let me put some numbers on this:

Task Manual time With n8n
Check inventory daily 10 min 0 min
Log orders to spreadsheet 15 min/day 0 min
Send abandoned cart emails 20 min/day 0 min
Read and tag reviews 30 min/week 0 min
Morning sales check 10 min/day 0 min

That’s over 3 hours per week handed back to you. For a store doing even $5,000/month, that’s time better spent on marketing, product development, or — honestly — not working.

What’s Next: Ready-Made Templates

If you want these workflows pre-built and ready to import — no JSON wrangling, no setup debugging — I packaged all 15 of my most-used n8n workflows into the FlowKit n8n Template Pack at stripeai.gumroad.com.

Each template includes the workflow JSON, a setup checklist, and a short video showing exactly how to configure the credentials. The Price Monitor ($29) and Daily Report Generator ($19) in the pack work directly with WooCommerce’s REST API.

Final Tips for WooCommerce + n8n

  1. Use webhooks over polling where possible — webhooks fire instantly, polling adds latency
  2. WooCommerce REST API rate limits: default is 200 requests/minute — more than enough for most stores
  3. Test with order status pending before going live with completed orders
  4. Store your API keys in n8n credentials (never hardcode them in Code nodes)
  5. Schedule triggers use server time — make sure your n8n instance timezone matches your store timezone

Questions? Drop them in the comments — I read every one.

What I Learned Building My Own AI Harness

Last month I spent some time building nano-harness, a small personal AI harness.

Not a framework, not the final agent platform that will replace everything and save humanity, or whatever people are saying this week.

Just a local-first desktop app to understand how these systems actually work when you stop talking about agents in abstract terms and start connecting the pieces yourself.

That was the real goal. I wanted to learn the shape of the thing. Provider adapters, local state, runs, tool calls, approvals, events, sessions, memory ideas, MCP, skills, all the boring plumbing behind the nice demos.

And like it usually happens, the project went further than planned.

The app runs on Electron, can be packaged for macOS, Linux, and Windows, has a local database (SQLite), a core runtime, provider abstractions, a run inspector, approval boundaries, and the beginning of a few bigger ideas that are still unfinished or behind feature flags.

Which is fine.

It was never supposed to become a huge product. It was a learning project, and I have a lot of fun building it.

Now I think it is time to wrap it up.

The Main Thing

The most useful thing I got from this project is not that AI can write code, we already know that. The useful part was seeing again how far you can go when you combine good software architecture with a good AI assistant.

No magic there. The assistant helps a lot. It keeps momentum, writes boring implementation details, moves code around, catches mistakes, proposes tests, and makes it easier to keep going when the project has too many small pieces.

But the assistant is not the architecture.

That part still matters, and honestly I think it matters more now, because AI makes it very easy to produce code faster than you can think about whether that code belongs in the right place.

If the structure is bad, AI helps you make the bad structure bigger. If the abstractions are confused, AI extends the confusion. If every feature leaks into every layer, the assistant will not save you from that, it will just help you create a more impressive mess.

But if the structure is clear, AI becomes much more useful.

In nano-harness, the important abstraction was the run.

A run is just a bounded attempt to satisfy one user request. It has messages, provider calls, actions, policy decisions, approvals, events, and persisted state. Nothing fancy, but that one idea makes the rest of the app easier to reason about.

That is the kind of abstraction I like. Not abstract because it sounds smart, abstract because it reduces the number of things you need to keep in your head.

The rest was the usual discipline:

  • the desktop app owns the UI
  • the core owns orchestration
  • the infra layer owns side effects
  • the shared package owns contracts

Nothing revolutionary, just useful, and useful beats clever almost every time.

The Buzzwords Get Smaller When You Build

There are a lot of words around AI development right now. Agents, harnesses, skills, MCP memory, spec-driven development, self-improving systems, context engines.

Some of those ideas are real, some are overloaded, some are early, and some are mostly marketing, until you try to build the small version and realize what they actually require. That is why I wanted to build my own version.

Not because nano-harness is better than mature tools. It is not. I use other tools every day, but building the small version gives you a different kind of knowledge. You stop treating the concept as magic and start seeing the cracks.

  • A provider is an adapter.
  • A tool call is a contract, an input schema, an execution boundary, and a result.
  • Memory is storage, recall, ranking, confidence, provenance, approval, and deciding what should not be remembered.
  • MCP exposes resources and tools, and immediately creates questions about filtering, credentials, safety, and context bloat.
  • Skills are reusable workflow packages, and the hard part is knowing when to inject them and when to leave the model alone.

Once you see it that way, the field becomes less intimidating and more interesting. It also becomes obvious that many of the hard parts are still the same old software engineering problems.

Boundaries, contracts, state, observability, safety, UX, debuggability, maintainability.

Same old friends, different interface.

The Tools

For the actual coding workflow, my tool of choice is still opencode.

I have tried almost everything at this point, and opencode is the one that feels best to me. It is open source, it is not trying to trap me inside one company, and it fits the way I like to work. That matters more to me every month.

For models, I used a combination of GPT-5.5 for planning and GPT-5.4 for execution. And this project reinforced something I already believed, once the plan is good enough, a lot of models can do useful execution work.

The quality of the plan matters a lot, if you ask the model to design the architecture, implement the feature, refactor the code, infer all missing context, and test everything at the same time, sometimes it works. Many times it creates very convincing nonsense.

I had better results when I used stronger models for planning, architecture, and review, and cheaper or local models for simpler execution tasks.

For some parts I even used a local model with MLX, specifically Qwen3.6-35B-A3B. Very impressive for its size.

No, it is not GPT-5.5 or Opus 4.7, it is not the model I would pick for the hardest architecture decisions. But if I showed myself one year ago that a local model could perform this well on real coding tasks, I probably would not believe it.

And that changes something.

Local Is Not a Toy Anymore

I have no doubt that the strongest models will stay inside big companies for a long time, and they will be useful for specific high-value tasks.

But a lot of normal work does not need the biggest possible model. A lot of company workflows are repetitive, structured, private, and full of local context. For that kind of work, open models and local execution are going to be enough in many cases.

That is not only a cost argument, although cost matters a lot with all the crazy pricing changes around AI tools lately. It is also a privacy argument.

We have sold our information very cheaply for a very long time. Maybe we should not repeat the same mistake with every repo, every document, every customer workflow, and every internal decision we feed into AI systems. Maybe we should do it better this time.

At least that is what I want to believe. This is why I keep paying attention to local inference with llama.cpp and MLX. Not because local models are always better, they are not. Because knowing when a local model is enough is becoming a real advantage.

Some useful skills for future engineering are model routing, workflow design, cost control, privacy boundaries, and knowing which model to use for which job.

Less sexy, much closer to real engineering.

What It Actually Taught Me

The most useful part of nano-harness was thinking about the abstractions.

  • What belongs in the core?
  • What belongs in infra?
  • What should be a local artifact?
  • What should be persisted?
  • What should require approval?
  • What should be visible in the UI?
  • What should never become a primitive too early?

That last one matters a lot. A lot of software gets worse because we promote ideas into primitives too fast.

You add one workflow, then another, then another, and suddenly the core knows about everything. Specs, memory, skills, projects, providers, UI modes, files, shell commands, and half the roadmap. Then every change becomes risky because every layer depends on every other layer.

I wanted to avoid that. So the rule became “keep the core small and boring”.

Most things should be compositions of existing primitives: runs, actions, policy, events, context, and local artifacts.

Spec-driven development does not need to be a new core. It can be a workflow built on runs.

Memory does not need to be shoved into every layer. It can be proposed from evidence and recalled with provenance.

Skills do not need to become a marketplace. They can start as local markdown packages.

MCP does not need to take over the product. It can sit behind filtering, inspection, and approval.

That was the fun part, not because the code is perfect, it is not. But because the project kept forcing small architectural decisions where I had to choose between adding power and preserving clarity. That is the real game.

Wrapping It Up

I am wrapping this project up now.

There is a lot more that could be done. A proper Spec Workbench, better memory, stronger skills, real MCP transport, better packaging, better docs, more benchmarks, a cleaner onboarding flow. Maybe a real release.

But not every project needs to become a product. Sometimes a project has already done its job when it changed how you think, nano-harness did that for me.

It made me more confident building AI-assisted tools. It made me more confident in my own architecture instincts. It gave me a clearer picture of what these harnesses are under the hood. And it reminded me again that every MVP leaves knowledge for the next one.

That knowledge compounds. Every time you finish something, even something small or unfinished or weird, the next thing starts from a better place.

There has never been a better time to try ideas, AI lowers the cost of exploring, refactoring, testing, and learning by doing. You can move faster, but you still need taste, judgment, know when to stop, and you still need to review the code and care about quality. No shortcuts.

Experiment, build the thing, fail a little, refactor, write down what you learned, repeat.

The best is yet to come.

Hytale Servers Will Fail Treasure Hunts Until We Fix Our Event Handling

The Problem We Were Actually Solving

At the time, we were trying to optimize our server for high latency environments. We wanted to make sure that our Treasure Hunt engine was stable and fast, even in the presence of network partitions or high packet loss. The goal was to minimize the impact of events on our server’s performance. But, as it often does, our optimization effort quickly spiraled out of control.

What We Tried First (And Why It Failed)

We initially went down the route of using a highly-optimized event bus library, one that promised to minimize the overhead of event dispatching. The idea was to use this library to decouple our event handling logic from the rest of the server, allowing us to scale our event processing independently of our main business logic. Sounds good in theory, but in practice, it was a nightmare. The library had a massive memory footprint, which, combined with our own server’s memory leaks, brought our system to its knees. The event bus library was also notoriously difficult to debug, making it almost impossible to pinpoint the root cause of our problems.

The Architecture Decision

After months of struggling with the event bus library, we were forced to take a step back and reassess our architecture. We realized that our main problem was not the event handling itself, but rather the way we were structuring our events. We were using a “publish-subscribe” model, where each event was broadcast to every subscriber, regardless of whether they needed it or not. This led to a huge amount of unnecessary event processing, which, in turn, caused our server to become overwhelmed.

To fix this, we switched to a more targeted approach, where events were only dispatched to relevant subscribers. We also introduced a caching layer to store event metadata, reducing the number of database queries and minimizing the overhead of event processing. And, most importantly, we rewrote our event handling logic to use a more efficient data structure, one that minimized memory allocations and reduced the overall latency of our event handling pipeline.

What The Numbers Said After

After implementing these changes, our system saw a significant improvement in performance. The Treasure Hunt engine, once a bottleneck that brought our server to its knees, was now able to handle thousands of concurrent requests without breaking a sweat. Our server’s memory usage dropped by 50%, and our latency improved by 90%. The load average, once a stubborn resident of the triple digits, now hovered around 1-2.

What I Would Do Differently

Looking back, I would have approached the problem differently from the start. Instead of trying to optimize our event handling system for high latency environments, I would have focused on building a more robust and scalable system from day one. I would have used a more targeted event handling model, one that minimized unnecessary event processing and reduced the overall load on our server. And, I would have avoided the event bus library altogether, opting for a more lightweight and custom-built solution. In the end, it’s a lesson learned: sometimes, the best approach is not to optimize for one specific use case, but to build a system that is inherently robust and scalable.

Redux in React: Managing Global State Like a Pro

Welcome back to Episode 12 of the “Let’s Master React Hooks Together” series

So far in this series, we’ve explored React hooks, component architecture, side effects, refs, memoization, and performance optimization. As React applications grow larger and more feature-rich, one major challenge starts appearing everywhere — managing state across multiple components efficiently.

What begins as simple prop passing between parent and child components can eventually turn into deeply nested component trees, duplicated state, inconsistent updates, and difficult-to-maintain code.

This problem becomes especially noticeable in real-world applications like:

  • E-commerce platforms
  • Social media applications
  • Admin dashboards
  • Analytics platforms
  • Project management tools

In these applications, multiple components often need access to the same shared data at the same time.

That’s exactly where Redux becomes valuable.

Redux helps React applications manage state in a centralized, predictable, and scalable way. Instead of scattering shared state across many components, Redux introduces a single source of truth that makes data flow easier to understand, debug, and maintain.

In this article, we’ll deeply understand Redux, how it works with React, why Redux Toolkit is now the standard approach, and how to structure Redux applications properly for modern frontend development.

By the end of this article, you’ll clearly understand:

  • What Redux is
  • Why Redux exists
  • Core Redux concepts
  • Redux Toolkit
  • Store setup
  • Actions and reducers
  • Async operations
  • RTK Query
  • Best practices
  • Common mistakes

What is Redux?

Redux is a predictable state management library used for JavaScript applications, especially React applications.

It provides a centralized place called a store where the entire application state is managed in a structured and organized way.

Instead of multiple components independently managing and duplicating shared data, Redux creates a single source of truth for the application.

This centralized architecture makes state changes easier to track, debug, test, and maintain as applications become larger and more complex.

Redux follows a strict one-way data flow, which makes application behavior more predictable and easier to understand during development.

Why Redux is Important

In modern frontend applications, multiple components often depend on the same piece of data.

For example:

  • Navbar → cart count
  • Product page → add to cart
  • Checkout page → cart items
  • Wishlist page → saved products
  • Profile page → user details
  • Dashboard → analytics data

Managing this shared data separately inside individual components quickly becomes difficult and error-prone.

Without centralized state management:

  • State duplication increases
  • Data synchronization becomes inconsistent
  • Prop drilling becomes excessive
  • Debugging becomes harder
  • Component communication becomes messy
  • Scaling the application becomes difficult

Redux solves these issues by introducing a centralized and predictable state container that every component can access consistently.

The Problem Redux Solves

Consider this component structure:

<App>
  <Dashboard>
    <Sidebar>
      <Profile />
    </Sidebar>
  </Dashboard>
</App>

If Profile needs user data stored inside App, the data must pass through every intermediate component.

This process is called prop drilling.

As applications grow, prop drilling creates several problems:

  • Components receive props they don’t actually use
  • Code becomes harder to maintain
  • Component trees become cluttered
  • State management becomes difficult

Redux removes this problem completely by allowing components to directly access centralized state from anywhere inside the application.

Redux Mental Model

You can think of Redux like a banking system:

  • Store → Bank database
  • Action → Request form
  • Reducer → Bank employee processing request
  • Dispatch → Submitting the request
  • Selector → Reading account information

This analogy helps visualize how data flows inside Redux applications.

Core Redux Concepts

To understand how Redux works internally, we first need to understand the five core building blocks that power the Redux architecture.

Redux mainly revolves around five concepts:

  1. Store
  2. Actions
  3. Reducers
  4. Dispatch
  5. Selectors

Let’s understand them one by one.

1. Store

The store is the central storage for the application state.

It acts as the brain of the application and contains the complete global state tree for the entire application.

Example:

{
  user: {
    name: "John"
  },

  cart: [],

  theme: "dark"
}

In this example:

  • user stores authentication or profile-related data
  • cart stores shopping cart items
  • theme stores UI preferences

The entire application state lives inside one centralized object, making shared data easier to manage consistently across the application.

2. Actions

Actions describe what happened inside the application.

They are plain JavaScript objects that tell Redux what type of state update should occur.

Example:

{
  type: "cart/addItem",

  payload: {
    id: 1,
    name: "Laptop"
  }
}

Here:

  • type identifies the action
  • "cart/addItem" describes adding an item to cart
  • payload carries extra data
  • id represents product ID
  • name represents product name

Actions help maintain predictable state updates because every state change is explicitly described.

3. Reducers

Reducers are pure functions responsible for determining how the state should change in response to specific actions.

They receive the current state and an action object, then return a brand-new updated state.

Reducers never perform API calls, side effects, or asynchronous operations directly because their behavior must remain predictable and consistent.

Example:

function cartReducer(state = [], action) {

  switch (action.type) {

    case "cart/addItem":
      return [...state, action.payload];

    default:
      return state;
  }
}

Here:

  • cartReducer is the reducer function
  • state = [] sets default cart state
  • action receives dispatched action object
  • switch(action.type) checks action type
  • "cart/addItem" handles cart item addition
  • [...state, action.payload] creates new updated array
  • default returns existing state if no match exists

Reducers are one of the most important parts of Redux because they define how state transitions happen in a predictable way.

4. Dispatch

Dispatch acts as the communication bridge between components and the Redux store.

Whenever a user performs an action like clicking a button, submitting a form, or updating data, components dispatch actions to Redux.

Redux then forwards those actions to reducers, which calculate the next state update.

Example:

dispatch({
  type: "cart/addItem",
  payload: product
});

Here:

  • dispatch() sends action to Redux store
  • type identifies the operation
  • payload contains product data
  • Reducer receives this action
  • Redux updates state automatically

5. Selectors

Selectors are used to read data from the Redux store.

They help components subscribe only to the specific pieces of state they need.

This improves performance because components re-render only when the selected state changes.

Example:

const cartItems = useSelector(
  (state) => state.cart
);

Here:

  • useSelector accesses Redux state
  • state represents complete Redux store
  • state.cart extracts cart data
  • cartItems stores extracted values

Selectors help keep components clean and focused by separating state access logic from UI rendering logic.

Redux Data Flow

Redux follows a strict one-way data flow architecture.

The flow looks like this:

  1. User interacts with UI
  2. Action gets dispatched
  3. Reducer processes action
  4. Store updates state
  5. UI re-renders automatically

This predictable structure makes applications easier to debug, scale, and maintain because every state update follows the same consistent flow.

What is Redux Toolkit?

Redux Toolkit is the official recommended way to use Redux today.

Earlier Redux implementations required developers to manually create:

  • Action types
  • Action creators
  • Reducers
  • Immutable update logic
  • Middleware configuration
  • Redux DevTools setup

This resulted in large amounts of repetitive boilerplate code.

Redux Toolkit was introduced to solve these problems and provide a modern, standardized, and simplified Redux development experience.

Benefits of Redux Toolkit:

  • Less code
  • Cleaner structure
  • Built-in best practices
  • Better readability
  • Simpler async handling
  • Automatic DevTools setup
  • Easier state updates

Redux Toolkit dramatically improves developer productivity while still following Redux principles internally.

Installing Redux Toolkit

npm install @reduxjs/toolkit react-redux
  • @reduxjs/toolkit installs Redux Toolkit
  • react-redux connects Redux with React
  • Both packages are required for React Redux applications

Creating a Redux Slice

A slice is one of the most important concepts in Redux Toolkit.

A slice groups together:

  • State
  • Reducers
  • Actions

This feature-based structure keeps Redux code cleaner and easier to scale.

Create counterSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  value: 0
};

const counterSlice = createSlice({

  name: "counter",

  initialState,

  reducers: {

    increment: (state) => {
      state.value += 1;
    },

    decrement: (state) => {
      state.value -= 1;
    },

    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const {
  increment,
  decrement,
  incrementByAmount
} = counterSlice.actions;

export default counterSlice.reducer;
  • createSlice creates Redux slice
  • initialState stores default values
  • value: 0 initializes counter
  • name: "counter" defines slice name
  • reducers contains state update functions
  • increment increases counter value
  • decrement decreases counter value
  • incrementByAmount updates value dynamically
  • action.payload receives custom value
  • counterSlice.actions auto-generates actions
  • export default counterSlice.reducer exports reducer

Slices significantly reduce Redux boilerplate and organize related logic in one place.

Why Direct Mutation Works Here

Inside Redux Toolkit:

state.value += 1;

At first glance, this looks like direct state mutation.

In traditional Redux, directly mutating state is strictly forbidden because Redux depends on immutable updates to detect state changes correctly.

However, Redux Toolkit internally uses a library called Immer.

Immer allows developers to write code that appears mutable while safely generating immutable updates behind the scenes.

So even though the syntax looks like mutation:

state.value += 1;

Redux Toolkit still preserves immutability internally.

This makes Redux code much cleaner and easier to write.

Configuring Redux Store

The Redux store holds the complete application state.

Create store.js

import { configureStore } from "@reduxjs/toolkit";

import counterReducer from "./counterSlice";

export const store = configureStore({

  reducer: {
    counter: counterReducer
  }
});
  • configureStore creates Redux store
  • counterReducer imports reducer
  • reducer object combines reducers
  • counter becomes state key
  • Entire application now accesses centralized state

Redux Toolkit’s configureStore also automatically enables useful development features like Redux DevTools and middleware support.

Connecting Redux to React

To make Redux available throughout the React application, React Redux provides a component called Provider.

Inside main.jsx

import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";

import { Provider } from "react-redux";

import { store } from "./store";

ReactDOM.createRoot(
  document.getElementById("root")
).render(

  <Provider store={store}>
    <App />
  </Provider>
);
  • Provider connects React with Redux
  • store={store} passes Redux store globally
  • <App /> gets access to Redux state
  • Every child component can now use Redux hooks

Without Provider, React components cannot access the Redux store.

Using Redux Inside Components

React Redux provides hooks that allow components to interact with Redux easily.

import React from "react";

import {
  useSelector,
  useDispatch
} from "react-redux";

import {
  increment,
  decrement
} from "./counterSlice";

function Counter() {

  const count = useSelector(
    (state) => state.counter.value
  );

  const dispatch = useDispatch();

  return (
    <div>

      <h1>{count}</h1>

      <button
        onClick={() => dispatch(increment())}
      >
        Increment
      </button>

      <button
        onClick={() => dispatch(decrement())}
      >
        Decrement
      </button>

    </div>
  );
}

export default Counter;
  • useSelector reads Redux state
  • state.counter.value accesses counter value
  • count stores extracted state
  • useDispatch returns dispatch function
  • dispatch(increment()) triggers increment action
  • dispatch(decrement()) triggers decrement action
  • UI automatically updates after state changes

This hook-based approach makes Redux integration significantly cleaner in modern React applications.

Recommended Redux Folder Structure

As Redux applications grow, organizing files properly becomes very important.

A feature-based structure improves scalability and maintainability.

src/

├── app/
│   └── store.js

├── features/
│   ├── counter/
│   │   ├── counterSlice.js
│   │   └── Counter.jsx

├── components/
  • app/ contains global store setup
  • features/ stores feature-based Redux logic
  • counter/ contains counter-related files
  • components/ stores reusable UI components

This structure keeps Redux logic modular and easier to scale in large applications.

Async Operations with createAsyncThunk

Real-world applications rarely work with static data.

Most applications fetch information from APIs such as:

  • User data
  • Product lists
  • Orders
  • Notifications
  • Dashboard analytics

Handling asynchronous operations manually in Redux can become complicated.

Redux Toolkit simplifies async logic using createAsyncThunk.

import {
  createSlice,
  createAsyncThunk
} from "@reduxjs/toolkit";

export const fetchUsers =
  createAsyncThunk(

    "users/fetchUsers",

    async () => {

      const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      );

      return response.json();
    }
  );
  • createAsyncThunk handles async logic
  • "users/fetchUsers" defines action name
  • async() performs API request
  • fetch() calls backend API
  • response.json() converts response into JSON
  • Returned data becomes payload automatically

createAsyncThunk also automatically generates loading, success, and error action types internally.

Redux DevTools

Redux DevTools is one of Redux’s most powerful debugging features.

It helps developers:

  • Track dispatched actions
  • Inspect state updates
  • Debug application flow
  • Replay state changes
  • Analyze application behavior

Redux Toolkit automatically enables Redux DevTools during development, making debugging significantly easier.

Redux vs Context API

React Context API can also handle shared state.

However, Redux provides several advanced capabilities that become valuable in larger applications.

Redux offers:

  • Better debugging tools
  • Middleware support
  • Predictable architecture
  • Advanced async handling
  • Improved scalability
  • Better developer tooling

Context API works well for:

  • Theme switching
  • Authentication state
  • Language preferences
  • Small shared states

Redux becomes more powerful when applications grow larger and state management complexity increases.

Common Redux Mistakes

Direct State Mutation

Wrong:

state.items.push(item);

Chain-by-chain breakdown:

  • Direct array mutation occurs
  • Original state gets modified
  • Traditional Redux does not allow this
  • Mutations can break predictable state updates

Correct:

return [...state, item];
  • Creates new array
  • Keeps original state immutable
  • Redux state updates safely

Understanding immutability is one of the most important concepts in Redux architecture.

RTK Query

RTK Query is one of the most powerful features introduced in Redux Toolkit.

It eliminates much of the manual API handling logic developers traditionally wrote themselves.

RTK Query automatically manages:

  • Data fetching
  • Caching
  • Loading states
  • Error handling
  • Background refetching
  • Request deduplication

This significantly reduces boilerplate and improves application performance.

Example:

import {
  createApi,
  fetchBaseQuery
} from "@reduxjs/toolkit/query/react";

export const api = createApi({

  reducerPath: "api",

  baseQuery: fetchBaseQuery({
    baseUrl: "https://api.example.com"
  }),

  endpoints: (builder) => ({

    getUsers: builder.query({

      query: () => "/users"
    })
  })
});

export const {
  useGetUsersQuery
} = api;
  • createApi creates API service
  • reducerPath defines Redux key
  • fetchBaseQuery sets base API URL
  • endpoints defines API operations
  • builder.query() creates GET request
  • query() returns endpoint path
  • useGetUsersQuery becomes auto-generated hook

RTK Query dramatically simplifies server-state management in modern React applications.

Best Practices

Use Redux Toolkit

Redux Toolkit is now the official standard for Redux development because it reduces boilerplate, improves readability, and includes built-in best practices.

Keep State Minimal

Only store data globally if multiple components truly need access to it.

Avoid storing unnecessary UI state inside Redux.

Use Feature-Based Structure

Keep related Redux logic together inside feature folders.

This improves scalability and maintainability.

Avoid Overusing Redux

Not every piece of state belongs in Redux.

Local UI states like:

  • Modal visibility
  • Form inputs
  • Dropdown toggles
  • Tooltip visibility

are usually better managed using useState.

Final Thoughts

This was Episode 12 of the “Let’s Master React Hooks Together” series, and Redux is one of the most important concepts to understand once React applications begin scaling.

Redux may initially feel complex because it introduces several new architectural concepts. However, once you understand the predictable flow between actions, reducers, and the store, managing large-scale React applications becomes significantly more organized and maintainable.

Modern Redux with Redux Toolkit has dramatically simplified the developer experience, making Redux cleaner, easier to learn, and far more practical for real-world applications.

The goal is not just learning Redux syntax, but understanding how scalable frontend architecture works in professional applications.

Once you become comfortable with slices, reducers, actions, selectors, and async handling, building complex applications becomes significantly easier and more structured.

In the upcoming episodes, we’ll continue exploring advanced React concepts, scalable frontend architecture, performance optimization patterns, and production-level development practices used in modern applications.

See you in the next episode of “Let’s Master React Hooks Together.”

Unfreezing Your GitHub Actions: Troubleshooting Stuck Deployments and Protecting Your Git Repo Statistics

The frustration of a stuck deployment, especially when using GitHub Actions for GitHub Pages, is a common pain point for developers. It’s not just about a delayed update; it impacts delivery timelines, developer morale, and skews crucial git repo statistics related to deployment success and efficiency. This isn’t merely an inconvenience; it’s a critical bottleneck that demands immediate attention from dev team members, project managers, and CTOs alike.

When GitHub Actions Workflows Freeze: The “Failed to Cancel” Enigma

Our discussion begins with a developer, Cubic-crypto, experiencing a persistent problem: a GitHub Actions workflow attempting to deploy to GitHub Pages remained “queued” for hours, failing to push edits from a Codespace. Attempts to cancel the run resulted in a frustrating “Failed to cancel workflow” error. This scenario, as highlighted by community member Gecko51, is a classic symptom of a broader GitHub Actions incident, where a runner is never assigned, leaving the workflow in a perpetual limbo. Such incidents can severely impact your team’s productivity and throw off your meticulously tracked git repo statistics.

Checklist for GitHub Actions workflow configurationChecklist for GitHub Actions workflow configuration

Initial Checks for Deployment Issues: Rule Out the Obvious

Before assuming a platform-wide issue, it’s always wise to rule out common configuration errors. Kunalmankar852 provided an excellent checklist for typical GitHub Pages deployment problems. These are the first steps any developer or delivery manager should take:

- **Pages Source Configuration:** Ensure your repository's `Settings > Pages` is correctly set to "GitHub Actions," not a specific branch. This is a frequent oversight that can halt deployments before they even begin.

**Missing Workflow Permissions:** Your workflow YAML needs explicit permissions for deployment. Without these, GitHub Actions simply won't have the authority to push changes. Look for:
    permissions:

contents: read
pages: write
id-token: write

**Incomplete Deploy Steps:** Verify your workflow includes the essential GitHub Pages actions. These actions handle the crucial steps of configuring, uploading, and deploying your site. Missing any of these will result in an incomplete or failed deployment:
    - uses: actions/configure-pages@v4
  • uses: actions/upload-pages-artifact@v3
  • uses: actions/deploy-pages@v4

    • Workflow Not Running on Correct Branch: Confirm that your on: push or on: workflow_dispatch trigger matches your default or deployment branch. A mismatch here means your workflow won’t even activate on the intended changes.
    • Build Output Folder Wrong: Ensure you upload the correct build directory (e.g., dist, build, public, etc.) in your upload-pages-artifact step. If the artifact isn’t found, there’s nothing for GitHub Pages to deploy.

Command line interface for force-canceling GitHub ActionsCommand line interface for force-canceling GitHub Actions

When It’s Not You: Incident Recovery Strategies

If you’ve checked all the above and your workflow is still stuck with a “Failed to cancel workflow” error, it’s highly likely you’re caught in a GitHub Actions incident. This is where technical leadership needs to step in with more advanced troubleshooting, as outlined by Gecko51:

**Force-Cancel via API:** The UI cancel button is often ineffective when a runner was never assigned. Instead, use the GitHub CLI to force-cancel the run. Replace `{owner}`, `{repo}`, and `{run_id}` with your specific values. The run ID is visible in the URL of the stuck workflow.
    `gh api -X POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel`

- **Disable and Re-enable Actions:** If the force-cancel doesn't work, a more drastic but often effective measure is to temporarily disable and then re-enable GitHub Actions for your repository. Navigate to `Settings > Actions > General`, disable, save, then toggle back on. This clears the queued state for your repo.

**Push an Empty Commit to Retrigger:** Once the GitHub Status page shows green and your workflows are unstuck, push an empty commit to kick off a clean deploy. This ensures a fresh workflow run without introducing new code changes:
    git commit --allow-empty -m "retrigger pages deploy"

git push

- Avoid Pushing During Incidents: A critical piece of advice: do not keep pushing real commits while things are stuck. Each push queues another run that will also get caught in the incident, prolonging your recovery time.

Impact on Productivity and Delivery: Beyond the Code

Stuck deployments have a cascading effect. For dev teams, it means wasted time, context switching, and a dip in morale. For product and project managers, it translates directly into missed deadlines and delayed feature releases. CTOs and delivery managers must recognize that these incidents directly impact key performance indicators (KPIs) tracked in a software kpi dashboard, such as deployment frequency, lead time for changes, and change failure rate. Accurate git repo statistics become vital here, but they are only useful if the underlying processes are reliable. When deployments are consistently failing or getting stuck, your metrics become misleading, masking deeper issues in your CI/CD pipeline.

Software KPI dashboard showing healthy git repo statistics and deployment metricsSoftware KPI dashboard showing healthy git repo statistics and deployment metrics

Proactive Measures and Lessons Learned for Technical Leadership

To mitigate the impact of such incidents and maintain high developer productivity, technical leaders should:

- Monitor GitHub Status: Regularly check GitHub's official status page during suspected outages. Subscribe to their updates for timely notifications.

  • Implement Robust CI/CD Health Checks: Beyond just checking if a build passed, integrate monitoring that tracks the entire deployment lifecycle. Tools that offer a comprehensive software kpi dashboard can provide visibility into your pipeline's health.

  • Diversify Critical Deployments (Where Applicable): While GitHub Actions is powerful, for mission-critical applications, consider a multi-pronged deployment strategy or robust fallback mechanisms.

  • Leverage Analytics for Deeper Insights: Tools that serve as a Pluralsight Flow free alternative can help analyze your team's workflow efficiency, identify bottlenecks, and provide actionable insights from your git repo statistics, ensuring that deployment issues are not just fixed, but prevented.

  • Empower Teams with Troubleshooting Knowledge: Ensure your team members are aware of these advanced troubleshooting steps, reducing reliance on a single point of contact during an incident.

Conclusion: Resilient Deployments for Peak Productivity

While platform incidents are sometimes unavoidable, our response to them defines our resilience. By understanding common configuration pitfalls and equipping ourselves with advanced recovery strategies, we can minimize downtime and protect our critical delivery metrics. For technical leaders, ensuring smooth, reliable deployments is paramount to fostering a productive development environment and maintaining accurate git repo statistics that truly reflect your team’s efficiency and impact.

Unlocking Project Discoverability on GHES: A Key to Software Engineering Productivity

GitHub Enterprise Server (GHES) is the backbone for many organizations, providing a secure, on-premise environment for their development efforts. It offers unparalleled control and customization, making it a critical asset for teams committed to innersource principles. Yet, a common question arises: do all the intuitive features of public GitHub.com, such as the comprehensive github.com/topics discovery page, translate directly to a GHES instance? A recent community discussion illuminated this very point, clarifying expectations and, more importantly, offering actionable strategies to significantly enhance project discoverability and, by extension, overall software engineering productivity on GHES.

The github.com/topics Experience: SaaS vs. On-Premise

The initial query from donny-dont highlighted a frequent source of confusion: attempting to navigate to HOSTNAME/topics on a GHES instance often results in a 404 error. This behavior can be perplexing, especially when some documentation hints at topics support. As community experts vshivam1729 and Gecko51 succinctly clarified, the rich, browsable github.com/topics interface is a feature exclusive to GitHub’s SaaS offering. It is not bundled with GitHub Enterprise Server.

The documentation references to HOSTNAME/topics are indeed accurate, but they pertain specifically to the REST API endpoint used for managing topics on individual repositories (e.g., /api/v3/repos/{owner}/{repo}/topics). They do not imply a user-facing interface for broad topic discovery. Consequently, the 404 is an expected response, and there is no hidden admin setting to simply “turn on” an Explore-style landing page on your GHES instance. Understanding this distinction is the first step toward building effective internal discovery mechanisms.

Comparison of GitHub.comComparison of GitHub.com’s topics browse page versus a 404 error on a GHES instance, illustrating the SaaS vs. on-premise feature difference.

Strategies for Boosting Development Productivity on GHES

While the direct equivalent of github.com/topics isn’t available, GHES users have several robust strategies to improve project discoverability, foster innersource practices, and ultimately elevate software engineering productivity. These approaches empower teams to find relevant codebases, documentation, and collaborators more efficiently, directly impacting their engineering performance goals.

1. Leverage Repository-Level Topics for Granular Discovery

Individual repositories on GHES fully support topics. This is a fundamental development productivity tool that’s often underutilized. Teams can tag their projects with relevant keywords, technologies, or team names directly through the repository UI or via the API.

- **Enhanced Searchability:** Topics are fully searchable within your GHES instance. A simple search query like `topic:innersource`, `topic:backend-service`, or `topic:your-team-name` in the global search bar will quickly filter repositories. This significantly reduces the time developers spend hunting for relevant projects, accelerating project onboarding and collaboration.

- **API Integration:** For more advanced use cases, the GHES API allows programmatic access to repository topics. This means you can build custom scripts or integrations to pull lists of repositories based on specific topics, feeding into internal dashboards or reporting tools. For example, `GET /api/v3/search/repositories?q=topic:innersource` can power a custom internal portal.

By consistently applying and maintaining repository topics, organizations create a self-organizing knowledge graph that directly contributes to faster development cycles and improved code reuse.

Illustration of a search bar on GitHub Enterprise Server with Illustration of a search bar on GitHub Enterprise Server with ‘topic:innersource’ entered, highlighting the use of repository topics for project discovery.

2. Build Curated Landing Pages with GitHub Pages

For a more structured and visually appealing discovery experience, the most common and effective GHES approach is to create a dedicated GitHub Pages site. This acts as an internal directory or portal.

- **Centralized Information Hub:** Host a simple markdown-based index in a dedicated repository. This site can serve as a landing page for key projects, documentation, team information, and even links to external resources, all grouped by relevant topics or categories.

- **Low Administrative Overhead:** Maintaining such a site is typically a manual effort but requires no special administrative changes to the GHES instance itself, making it appealing to admins wary of new features or organizations. It leverages existing GHES capabilities without adding complexity.

- **Innersource Showcase:** This approach is particularly powerful for fostering innersource. It provides a clear, curated entry point for developers looking to contribute to internal projects, promoting visibility and collaboration across teams.

A well-maintained GitHub Pages site can significantly reduce friction in discovering internal assets, acting as a powerful development productivity tool that guides users through your organization’s codebase landscape.

3. Strategic Use of Dedicated “Discovery” Organizations

Admins are often cautious about creating new organizations to prevent sprawl. However, a single, strategically designed “discovery” or “innersource” organization can be a highly effective solution.

- **Minimal Footprint, Maximum Impact:** Instead of multiple new orgs, propose a single organization dedicated to housing key discovery assets. This could include the GitHub Pages repository mentioned above, as well as repositories that serve as meta-indexes or project manifests.

- **Clear Purpose and Structure:** With a well-maintained README and judicious use of pinned repositories, this org can provide a clear, concise overview of your most important internal projects. It addresses the admin's concern about sprawl by centralizing discovery efforts rather than fragmenting them.

- **Achieving Engineering Performance Goals:** This structured approach directly supports `engineering performance goals` by making it easier for new hires to onboard, for teams to find reusable components, and for leadership to gain an overview of active projects.

Image depicting a custom internal project portal built using GitHub Pages on GHES, showcasing curated content for easy discoverability.Image depicting a custom internal project portal built using GitHub Pages on GHES, showcasing curated content for easy discoverability.

Engage Your GHES Admins and GitHub Enterprise Support

Feature availability can vary significantly across GHES versions. While the core mechanisms described above are generally consistent, it’s always prudent to engage your GHES administrators. They can provide insights into your specific instance’s configuration, any custom integrations, or future upgrade plans.

Furthermore, opening a ticket with GitHub Enterprise Support is a valuable step. They can confirm the exact status of topic browsing features for your GHES version and offer official guidance on best practices for internal discoverability. This proactive engagement ensures you’re leveraging your GHES investment to its fullest potential, aligning with broader engineering performance goals for your organization.

Conclusion: Empowering Your Teams on GHES

The absence of a public github.com/topics-style page on GHES is not a limitation but an opportunity for tailored, internal solutions. By thoughtfully implementing repository topics, building curated GitHub Pages portals, and strategically organizing discovery assets, organizations can create a robust ecosystem for project visibility. These strategies are more than workarounds; they are foundational development productivity tools that empower your teams, streamline innersource adoption, and ultimately drive significant improvements in overall software engineering productivity. Embrace these approaches to transform your GHES instance into a highly discoverable, collaborative, and efficient development hub.

I Stacked 4 More Context Layers on Top of RAG. Sonnet Got 12% Better. Haiku Got 14% Worse.

Full CE vs RAG: Score deltas across Sonnet and Haiku

I read a post about “Full Context Engineering” and immediately added four more layers to my RAG pipeline. Structured output instructions. Hierarchical document layout. Role definition. Few-shot examples. The whole buffet.

The improvement on Claude Sonnet was 12%.

The improvement on Claude Haiku was minus 14%.

I had just spent two weeks building scaffolding to make my smaller model worse at its job. If you have ever wallpapered a room and stepped back to discover you covered up the light switch, you know the feeling.

This post is about what those numbers actually mean for the way you spend your context engineering effort in 2026.

What I was measuring

I was running a benchmark against my own book corpus for a previous experiment (the cheap-model post). The same scoring rubric: factual accuracy, hallucination rate, specificity, and honesty on a 0 to 15 scale.

The configurations were a ladder. Each rung adds one more thing on top of the previous one.

  1. System prompt only: the bare baseline. No retrieval, nothing.
  2. System + RAG: vector search over a curated corpus, top documents injected.
  3. Full Context Engineering: RAG + structured output instructions + hierarchical layout + role definition + few-shot examples.

What I expected: a smooth upward curve. What I got was a curve that leaned forward and then fell over.

The numbers

Full CE vs RAG: Score deltas across Sonnet and Haiku

Claude Sonnet, total score (out of 15):

Configuration Total Delta from previous
System only 8.8 —
System + RAG 10.2 +1.4 (+16%)
Full Context Engineering 11.4 +1.2 (+12%)

Claude Haiku, total score (out of 15):

Configuration Total Delta from previous
System only 3.7 —
System + RAG 11.8 +8.1 (+219%)
Full Context Engineering 10.1 -1.7 (-14%)

Two findings I did not expect.

First: RAG is doing almost all of the work. On Sonnet, RAG closed 88% of the gap between baseline and the fully tricked-out pipeline (1.4 of the total 2.6 point improvement). On Haiku, RAG over-shot the final number entirely.

Second: stacking more on top of RAG is not free. On Haiku, it actively made things worse. The hallucination score went from 1.7 to 0.5. The honesty score went from 1.3 to 0.5. The model started confidently making things up that it had previously hedged on.

Why this happens

I have a hypothesis that I think survives contact with reality.

A small model has limited working memory. RAG hands it the right facts. Once those facts are in front of it, the marginal returns from extra structure are small. But the marginal cost of extra context is not small. Every paragraph of role definition, every few-shot example, every “here is how to format the output” block competes with the retrieved documents for the model’s attention.

For Sonnet, the working memory is wide enough that the extras land in unused space. For Haiku, the extras shove the actually-useful retrieved context off to the edge of the window. The model still sees it. It just stops trusting it.

This is the same finding that recent research on long-context behavior keeps surfacing. Studies on instruction-following at high context fill report that for most frontier models in 2026, quality starts to degrade measurably at 60 to 70 percent context fill, and falls off a cliff around 90 percent. The cliff is steeper for smaller models.

The Pareto principle applies to context engineering with embarrassing accuracy. RAG is the 20 percent of effort that produces 80 percent of the result. Everything you stack on top of it is the long tail.

The 2026 reality I almost forgot to mention

When I ran the original experiment, I was on Sonnet 4 and Haiku 3 with a 200K context window. As of this writing, Sonnet 4.6 has a 1M token context window at standard pricing and prompt caching cuts the cost of repeated context by 90 percent.

This changes the math, but not in the direction you might think.

A 1M context window does not magically make stacked context cheaper to design. The model still has to pay attention to the right thing. The cliff at 60 to 70 percent fill is a percentage, not an absolute. A bigger window just means you can write more bad context before you fall off it.

Prompt caching helps if your stacked layers are static. The role definition, the few-shot examples, the structured output instructions: those parts cache cleanly. But that only saves money. It does not save quality. If your Haiku result was minus 14%, prompt caching makes minus 14% cheaper. That is not the win you wanted.

The thing nobody told me about Skills

Anthropic’s Skills feature is interesting in this light. Skills are reusable context bundles that load on demand. The right way to think about them is not “more context, all the time” but “the right context, just in time.”

That is the failure mode my Full CE experiment ran into. I was packing every layer into every request. Skills point at the alternative: keep the system prompt small, retrieve the relevant skill, and let the rest stay out of the window. It is the same lesson as RAG, applied one level up. Selective beats throwing everything in.

What I do now

If you take only one thing from this post, take this: the order of operations matters more than the number of operations.

  1. Build the retrieval first. Get RAG working with a clean corpus, decent embeddings, and a relevance threshold. This is your 80%.
  2. Run a benchmark. Real benchmark, on real questions, scored by a real rubric. Not vibes.
  3. Add one layer at a time. Structured output, then hierarchical layout, then role definition. Re-benchmark after each.
  4. If the score goes down, take that layer out. Do not assume the layer is good and your benchmark is bad. The benchmark is right more often than you think.
  5. Try the same ladder on a smaller model. The thing that helps Sonnet may hurt Haiku. Knowing which side of the line you are on saves you money.

This sounds obvious. It is not what most teams do. Most teams read a blog post about Context Engineering, add four layers in one weekend, and never measure whether the layers actually helped.

The chef and the kitchen, revisited

In an earlier post I wrote that the model is the chef and the context is the kitchen. I want to extend that.

Adding more context layers is like installing more kitchen equipment. A second oven. A pasta machine. A sous-vide. None of them make the chef worse at cooking pasta. But if the pasta machine takes up the counter space where the chef was chopping vegetables, the dinner gets worse anyway.

The chef does not need every appliance. The chef needs the right ingredients within reach.

Before you read the next breathless post about Full Context Engineering and start adding layers, run the experiment. Measure RAG alone. Measure RAG plus one thing. Find the layer that earns its keep, and leave the rest in the catalog.

The answer is almost always: do RAG well first. Everything else is decoration. Decoration that, on a small model, can flip the sign on your accuracy score and leave you wondering why.

The next time someone says “Context Engineering,” what I want to say back is: please define which 20 percent of context you mean. The other 80 has a good chance of making things worse.

The full Context Engineering system (five strategies, the RAG benchmarks behind these numbers, MCP server design, and the Agentic RAG implementation) is in Turning LLMs from Liars into Experts: Context Engineering in Practice.