Vue 3 Simple Component Development: API Design from the Button Component
12 Jun 2026, 6:00 amMinimalism is not crudeness, restraint is not deficiency — drawing inspiration from Nuxt UI v4, how to design a well-crafted component API
This article covers: Props definition, variant system (variant + color), size trade-offs (why remove xs/xl), slot design (label prop vs default slot), loading state handling (including CLS prevention), accessibility attributes, and a comparison table with mainstream UI libraries.
I. Background and Reference
In previous articles, we discussed the concept of design tokens over atomic CSS, and the architecture of CSS-first + thin component wrappers. But one question remains unanswered: How should a single component's API be designed?
Early in the design process, I deeply referenced Nuxt UI v4's approach. Nuxt UI v4 consolidates complex styles and interactions into a few core dimensions — variant/color/size. This is exactly what I wanted: encapsulate complexity inside the component, exposing only the most streamlined API to the outside.
This article uses the Button component as an example to walk through my design trade-offs and thought process step by step.
II. Starting from Requirements: What Does a Button Need?
A button component's most basic functionality:
- Display text
- Trigger events on click
- Disabled state
- Different styles (primary, secondary, danger, etc.)
But is that enough? Let's look at real usage scenarios:
<!-- Button with icon -->
<Button>
<template #icon>🔍</template>
Search
</Button>
<!-- Loading state -->
<Button loading>Submitting</Button>
<!-- Block button (full width) -->
<Button block>Full Width Button</Button>
<!-- Different sizes -->
<Button size="sm">Small</Button>
After analysis, the Button component needs to support:
| Requirement | Implementation |
|---|---|
| Text content | Default slot or label prop |
| Click event |
click event |
| Disabled state |
disabled prop |
| Loading state |
loading prop |
| Different styles |
variant + color
|
| Different sizes |
size prop |
| Block width |
block prop |
| Icon |
#icon slot |
III. Props Design: Types, Defaults, Priority
3.1 Base Props
interface Props {
label?: string; // Button text
disabled?: boolean; // Disabled state
loading?: boolean; // Loading state
block?: boolean; // Block level
}
Default value design:
const props = withDefaults(defineProps<Props>(), {
label: "",
disabled: false,
loading: false,
block: false,
});
3.2 Variant System: variant + color
Common button types include: primary, secondary, outline, ghost. Inspired by Nuxt UI v4's variant + color design, I chose to completely decouple "visual mode" from "semantic color".
type Variant = "filled" | "outline";
type Color = "primary" | "success" | "warning" | "error";
Why keep only filled and outline, removing ghost?
| Variant | Usage Frequency | Keep? |
|---|---|---|
filled |
🔥🔥🔥🔥🔥 Extremely high | ✅ Keep |
outline |
🔥🔥🔥🔥 High | ✅ Keep |
ghost |
🔥 Low | ❌ Removed (can be replaced by outline) |
Similarly, keep only 4 colors:
| Color | Usage Frequency | Keep? |
|---|---|---|
primary |
🔥🔥🔥🔥🔥 Extremely high | ✅ Keep |
success |
🔥🔥🔥 Medium | ✅ Keep |
warning |
🔥 Low | ✅ Keep |
error |
🔥🔥 Medium | ✅ Keep |
neutral |
🔥 Low | ❌ Removed (can be replaced by outline) |
3.3 Size Design
type Size = "sm" | "md" | "lg";
Compared with Nuxt UI v4's five sizes (xs, sm, md, lg, xl), I made a simplification. I removed xs and xl because the smallest use cases can be covered by Badge or other non-button components, and I rarely encounter extra-large buttons in a personal blog context.
Default size selection: Mainstream UI libraries (Naive UI, PrimeVue, etc.) have default button heights around 32-34px, which corresponds to our sm, so the default size is sm.
const props = withDefaults(defineProps<Props>(), {
size: "sm", // default small
});
3.4 Icon Design: Only Use Slots
To keep things minimalist, the Button component does not provide an icon prop, only the #icon slot. When users need an icon, they simply put content in the slot:
<Button>
<template #icon>🔍</template>
Search
</Button>
Although this requires a few more characters, it avoids confusion between prop and slot priority, and better follows the single responsibility principle. Also, the slot can accept any content (strings, emojis, icon components), with complete flexibility.
Design consideration: Nuxt UI v4 provides an
iconprop and multiple icon-related attributes (leading-icon/trailing-icon). But in a personal blog context, the vast majority of icon buttons are simply "icon + text", and using a slot is sufficient for all use cases, reducing learning and maintenance costs.
IV. Slot Design: Default Slot vs label Prop
To support both quick writing and custom content, provide both label prop and default slot:
<span class="mg-button-label">
<slot>{{ label }}</slot>
</span>
- If default slot content is provided, display it
- Otherwise, display the
labelprop
Both usage styles are supported:
<!-- Using label prop -->
<Button label="Submit" />
<!-- Using default slot -->
<Button>Submit</Button>
V. State Handling
5.1 Disabled State
Both disabled and loading disable the button:
const isDisabled = computed(() => props.disabled || props.loading);
<button :disabled="isDisabled">
5.2 Loading State
Show a spinning animation when loading, hide icon and text:
<template v-if="loading">
<span class="mg-button-loading-icon" />
</template>
<template v-else>
<span v-if="hasIconSlot" class="mg-button-icon">
<slot name="icon" />
</span>
<span v-if="hasLabel" class="mg-button-label">
<slot>{{ label }}</slot>
</span>
</template>
Pure CSS loading animation:
.mg-button-loading-icon {
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: mg-button-spin 0.6s linear infinite;
}
@keyframes mg-button-spin {
to {
transform: rotate(360deg);
}
}
VI. CSS Styling
6.1 Base Styles
Buttons use inline-flex layout, content centered both horizontally and vertically, with straight corners (--ui-radius-none), padding and font size using design tokens.
.mg-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--ui-spacing-sm);
font-weight: 500;
transition: all var(--ui-motion-duration-neural) ease;
cursor: pointer;
border-radius: var(--ui-radius-none);
border: none;
background: transparent;
white-space: nowrap;
padding: var(--ui-spacing-sm) var(--ui-spacing-md);
font-size: var(--ui-typography-size-body);
}
6.2 Size Variants
| Size | Padding | Font Size |
|---|---|---|
sm |
sm / md
|
--ui-typography-size-code (13px) |
md |
md / lg
|
--ui-typography-size-body (15px) |
lg |
lg / xl
|
1.125rem (18px) |
.mg-button-sm {
padding: var(--ui-spacing-sm) var(--ui-spacing-md);
font-size: var(--ui-typography-size-code);
}
.mg-button-md {
padding: var(--ui-spacing-md) var(--ui-spacing-lg);
font-size: var(--ui-typography-size-body);
}
.mg-button-lg {
padding: var(--ui-spacing-lg) var(--ui-spacing-xl);
font-size: 1.125rem;
}
6.3 Block Button
.mg-button-block {
width: 100%;
}
6.4 Icon and Label Containers
The icon container uses inline-flex with line-height: 0 to eliminate line-height influence. SVG or iconify icons inside are forced to block display with width/height set to 1em.
.mg-button-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
line-height: 0;
}
.mg-button-icon svg,
.mg-button-icon .iconify {
display: block;
width: 1em;
height: 1em;
}
.mg-button-label {
display: inline-flex;
align-items: center;
}
6.5 Solving Icon-Only Button Centering
When a button has only an icon and no text, the empty .mg-button-label occupies space and breaks centering. Hide empty labels with the :empty pseudo-class:
.mg-button-label:empty {
display: none;
}
Also check for real content before rendering the label:
const hasLabel = computed(() => !!props.label || !!slots.default);
<span v-if="hasLabel" class="mg-button-label">
<slot>{{ label }}</slot>
</span>
6.6 Variant Styles
filled and outline variants correspond to filled background and transparent background with border, respectively. Colors are dynamically combined via the color prop. For example, .mg-button-filled-primary uses --ui-primary as background color.
VII. Accessibility
Add ARIA attributes to improve accessibility:
<button
:aria-busy="loading"
:aria-disabled="disabled || loading"
>
Screen readers can correctly read the button's loading and disabled states.
VIII. Attribute Inheritance
Use v-bind="$attrs" to pass through native attributes:
<button v-bind="$attrs">
Users can directly pass attributes like id, name, data-*, aria-*:
<Button id="submit-btn" name="submit" data-testid="submit">
Submit
</Button>
IX. Final Code
<template>
<button
v-bind="$attrs"
class="mg-button"
:class="[
`mg-button-${variant}-${color}`,
`mg-button-${size}`,
{ 'mg-button-block': block, 'mg-button-loading': loading },
]"
:disabled="disabled || loading"
:aria-busy="loading"
:aria-disabled="disabled || loading"
@click="handleClick"
>
<template v-if="loading">
<span class="mg-button-loading-icon" />
</template>
<template v-else>
<span v-if="hasIconSlot" class="mg-button-icon">
<slot name="icon" />
</span>
<span v-if="hasLabel" class="mg-button-label">
<slot>{{ label }}</slot>
</span>
</template>
</button>
</template>
<script setup lang="ts">
import { useSlots, computed } from "vue";
const slots = useSlots();
const hasIconSlot = computed(() => !!slots.icon);
const hasLabel = computed(() => !!props.label || !!slots.default);
type Variant = "filled" | "outline";
type Color = "primary" | "success" | "warning" | "error";
type Size = "sm" | "md" | "lg";
interface Props {
label?: string;
variant?: Variant;
color?: Color;
size?: Size;
disabled?: boolean;
loading?: boolean;
block?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
label: "",
variant: "filled",
color: "primary",
size: "sm",
disabled: false,
loading: false,
block: false,
});
const emit = defineEmits<{ click: [event: MouseEvent] }>();
const handleClick = (event: MouseEvent) => {
if (props.disabled || props.loading) return;
emit("click", event);
};
</script>
X. Comparison with Mainstream UI Libraries
| API Feature | Moongate UI | Nuxt UI v4 | Naive UI (NButton) | PrimeVue (Button) |
|---|---|---|---|---|
| Core style |
variant (filled/outline) |
variant + color
|
type |
severity + variant
|
| Size |
size (sm/md/lg) |
size (xs/sm/md/lg/xl) |
size (small/medium/large) |
size (small/medium/large) |
| Disabled | disabled |
disabled |
disabled |
disabled |
| Loading | loading |
loading / loadingAuto
|
loading |
loading |
| Block | block |
block |
block |
fluid |
| Icon |
#icon slot |
icon / leading-icon / trailing-icon + slot |
None |
icon + iconPos
|
| Extra styles | None | square |
dashed, circle, round
|
rounded, raised, outlined
|
The comparison clearly shows:
-
Decoupling
variantandcolor:variantfocuses on "visual mode",colorfocuses on "semantic meaning" - Streamlined sizing: 3 sizes are sufficient for personal blog scenarios
-
Icon flexibility: The
#iconslot achieves the same effect as props, with more flexibility -
Semantic naming: Using
filledandoutlinemore accurately describes the visual style
XI. Additional Detail: The Width Change Trap in Loading State
There's a common but easily overlooked issue when using the loading attribute — "button width changes when loading". When the loading animation (e.g., a spinner) appears, it changes the button's internal children, usually causing the container to widen and layout shifts. This is essentially Cumulative Layout Shift (CLS).
An elegant solution: reserve space with min-width, and absolutely position the loading icon so it doesn't affect layout.
.mg-button {
/* 1. Reserve enough space to ensure consistent width in loading and normal states */
min-width: 88px;
}
.mg-button-loading .mg-button-label {
opacity: 0;
}
.mg-button-loading-icon {
/* 2. Absolutely position the loading icon centered, without affecting layout */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
XII. Design Decision Summary
| Decision | Reason |
|---|---|
Remove xs size |
Low usage frequency, simplify API |
Remove ghost variant |
Can be replaced by outline
|
Remove neutral color |
Can be replaced by outline + default color |
Default size sm
|
Mainstream UI libraries default to ~32px |
Only use #icon slot |
Avoid prop/slot confusion, keep it simple |
label prop + default slot |
Support both writing styles |
| Hide empty label | Solve icon-only button centering |
v-bind="$attrs" |
Pass through native attributes, maintain flexibility |
min-width + absolute positioned loading icon |
Prevent layout shift during loading |
The original Chinese version is available on my blog: moongate.top.
Try the component library on npm: moongate-vue
Explore the component documentation: vue.moongate.top
© 2026 yuelinghuashu. This work is licensed under CC BY-NC 4.0.
Using AWS DevOps Agent with Terraform and CI/CD Pipelines
12 Jun 2026, 6:00 amThere’s no shortage of AI tools claiming to improve software delivery. Most of them focus on code generation. Some focus on chat interfaces. A few attempt to automate operational tasks.
What caught my attention about AWS DevOps Agent is that it focuses on something infrastructure teams spend a surprising amount of time doing every day: understanding changes.
As infrastructure grows, engineers spend less time writing Terraform and more time reviewing it, explaining it, validating it, and assessing risk before deployment.
That’s where I think tools like AWS DevOps Agent become interesting. Not because they replace engineers. But because they can reduce some of the cognitive load around infrastructure workflows.
Imagine a fairly typical Terraform pull request.
A developer modifies an EKS node group. Updates autoscaling limits.
Makes a few IAM changes. The Terraform plan is generated as part of a GitLab CI pipeline. The output may easily contain hundreds of lines.
Terraform will perform the following actions:
# aws_eks_node_group.platform
~ scaling_config
desired_size: 3 -> 6
# aws_iam_role.platform
~ inline_policy
Nothing unusual.
But someone still needs to understand:
What changed?
Is there downtime risk?
Is there cost impact?
Is there a security implication?
This is where an AI agent can be useful. Instead of asking engineers to parse raw plan output, the agent can generate a human-readable summary.
Something closer to:
The node group capacity is being doubled from three to six nodes.
This may increase cluster costs but improves available capacity.
An IAM policy is also being modified. Review permissions carefully before applying.
The Terraform hasn’t changed. The deployment process hasn’t changed. But the review process becomes easier.
The same idea applies to CI/CD pipelines. A typical deployment pipeline already produces a large amount of information.
- Build logs.
- Security scans.
- Terraform plans.
- Kubernetes deployment outputs.
- Test results.
Most of this information is technically available. The challenge is understanding it quickly. An AI agent can act as an additional layer between the pipeline and the engineer.
Instead of scrolling through hundreds of lines of output, the engineer receives a concise explanation of what happened. Like having a senior teammate summarize the deployment.
Where I find this particularly interesting is that it aligns with a pattern I’ve written about several times before. The most useful AI systems in DevOps are often the ones that improve understanding rather than execution.
I am generally cautious about giving AI systems the authority to make infrastructure decisions.
I am much more comfortable letting them explain Terraform plans, summarize deployment risks, or surface unusual changes. That’s a very different trust boundary. One accelerates human decision-making. The other attempts to replace it.
I suspect this is where tools like AWS DevOps Agent will find their strongest use cases. Not by automatically deploying infrastructure. Not by replacing platform engineers. But by helping teams understand increasingly complex systems faster.
And as anyone who has spent time reviewing Terraform plans or deployment pipelines knows, understanding the change is often the hardest part.
Note: This article was written with the assistance of AI tools for structuring and drafting. The ideas, examples, and perspectives are based on real-world experience in DevOps and cloud engineering.
Python Regex Explained Simply — Extract Anything From Text
12 Jun 2026, 5:59 amRegex sounds intimidating. It is not. Once you understand the 5 core concepts, you can extract any pattern from any text in seconds. Here is everything you need to know.
What is regex?
Regex is a pattern language. You describe what you are looking for using special characters and Python finds it for you — in any block of text, any size.
Real example: your client sends you a document with 500 customer records mixed with random text. They need all email addresses extracted into Excel. Without regex this takes hours. With regex it takes 3 lines.
import re
text = "Contact john@gmail.com or sales@company.com for details"
emails = re.findall(r'[\w.-]+@[\w.-]+\.\w+', text)
print(emails)
# ['john@gmail.com', 'sales@company.com']
The 5 patterns you need to know
1. \d — any digit
re.findall(r'\d', 'abc123def456')
# ['1', '2', '3', '4', '5', '6']
2. \w — any word character (letter, digit, underscore)
re.findall(r'\w+', 'hello world_123')
# ['hello', 'world_123']
3. + — one or more of the previous
re.findall(r'\d+', 'price is 45000 and tax is 8100')
# ['45000', '8100']
4. [] — any character in this set
re.findall(r'[aeiou]', 'hello world')
# ['e', 'o', 'o']
5. . — any single character
re.findall(r'c.t', 'cat cut cot bat')
# ['cat', 'cut', 'cot']
The 3 functions you will use constantly
re.findall — find all matches
Returns a list of everything that matches the pattern.
text = "Prices: ₹45,000 and ₹12,500 and ₹8,750"
prices = re.findall(r'[\d,]+', text)
print(prices)
# ['45,000', '12,500', '8,750']
re.sub — find and replace
Replaces every match with something else.
messy = "phone: 98-765-43210"
clean = re.sub(r'\D', '', messy) # remove all non-digits
print(clean)
# '9876543210'
re.search — find first match
Returns just the first match with its position.
text = "Order #A12345 placed successfully"
match = re.search(r'#(\w+)', text)
if match:
print(match.group(1)) # A12345
A real data cleaning example
Client problem: they have a spreadsheet with phone numbers in 6 different formats. They need them all standardised to 10 digits.
import pandas as pd
import re
df = pd.DataFrame({
'Phone': ['9876543210', '+91-9876543210',
'(080) 4567-8901', '91 98765 43210']
})
def clean_phone(phone):
digits = re.sub(r'\D', '', phone)
if len(digits) == 10:
return digits
elif len(digits) == 12 and digits.startswith('91'):
return digits[2:]
return None
df['Clean'] = df['Phone'].apply(clean_phone)
print(df)
Output:
Phone Clean
0 9876543210 9876543210
1 +91-9876543210 9876543210
2 (080) 4567-8901 None
3 91 98765 43210 9876543210
The one-line summary
Regex is a pattern language — you describe what you are looking for and Python finds every instance of it in any text, any size.
Learn these 5 patterns and 3 functions and you can handle 90% of real data extraction gigs immediately.
Written by Raaga Priya Madhan — CSE student, Bangalore. I build Python automation and data extraction scripts. See my work on GitHub and connect on LinkedIn
Software Development as a Philosophical Act: Beyond Syntactic Sugar
12 Jun 2026, 5:49 amRecently, a reader named Alton Zheng left a comment on my previous post, asking a challenging question: How do you approach teaching or mentoring others to maintain an "old school" engineering mindset in today’s AI-driven environment?
This made me reflect deeply on my years as an instructor. Long before the advent of Large Language Models, I always operated under a core principle: software development — when understood not merely as typing syntax, but as architecting solutions — is fundamentally rooted in philosophy. I am not talking about textbook academic philosophy, though perhaps that would help too. I am talking about a structural mindset. Of course, as engineers, we must eventually translate thought into practice and deliver a concrete, working result. But the philosophical approach to a problem is what wins the long game.
Years ago, when I was teaching Visual Basic, I constantly drilled into my students that searching for a solution is entirely independent of the programming language. The language itself is just an implementation detail, a tool to be studied.
During the first week or two, some students would actually protest. They would look at me and say, "You aren't teaching us." They expected predefined standards, snippets to copy, and rote memorization. But I refused to give them the answers. I enforced boundaries, models, and constraints. Over time, as they realized that the solutions to complex bugs and logic loops were suddenly originating from their own reasoning without my intervention, they finally understood.
They weren't just learning to code; they were learning how to think.
The Core: Seeing the Forest, Not Just the Trees
In my career, the time spent typing code has always been a fraction of the time spent analyzing the problem and exploring potential paths. A trained engineering mind doesn't just fix the immediate bug or implement the current feature request; it looks at how the system will evolve over time. It anticipates the client's next needs by looking at the whole forest, not just the single tree. It requires freeing your imagination to find non-conventional solutions or to refine existing ones.
This is the exact opposite of how modern AI operates. LLMs predict the next token or the next line of code based on a statistical distribution derived from existing literature. By definition, AI operates within the boundaries of the statistically probable. But in real-world engineering, the standard, 'statistically probable' solution is rarely the elegant, tailor-made architecture your specific context demands.
One might argue that AI's randomness can sometimes mimic human intuition, occasionally assembling existing pieces into a novel puzzle no one has thought of before. But there is a fundamental difference: AI's randomness is a blind statistical roll of the dice. Human intuition, on the other hand, is a purposeful leap driven by an understanding of constraints. AI can generate a thousand unpredictable combinations, but it lacks the contextual judgment to know which puzzle actually makes engineering sense. It can suggest the pieces, but the architect must still design the picture.
Analyzing and verifying requirements is a continuous mental exercise. Even if your reasoning ultimately leads you to rewrite standard, well-known patterns, the process itself is invaluable. It is the mental gym that keeps your brain flexible, disciplined, and ready for the unexpected.
Language is a Detail, Autonomy is the Goal
If you focus solely on the syntax of a language—whether it was Visual Basic decades ago, a trendy JavaScript framework last week, or an AI prompt today—you are trapping yourself in a commodity skill. When you look at a problem from a high architectural level, the language becomes a mere deployment detail.
As a mentor, my goal has never been to hand out ready-made snippets. It is to force the developer to step back and look at the structural layout. That "click" moment—when a junior developer realizes they solved a complex architecture loop entirely on their own—is the moment they transition from being a simple executor to a true professional.
In the AI era, this shift is critical. If you only know syntax, you are letting the AI do the thinking, which makes you obsolete. If you master structural reasoning, you learn how to learn. The AI becomes your high-speed compiler, but you remain the sole custodian of the system design.
Case Study: GemmaLink – Architecture Wins Over Syntax
I had a chance to put this philosophy to the test during a recent challenge on DEV.to. The goal was to build an application leveraging Google's lightweight Gemma models.
Instead of getting hypnotized by the AI model itself, I started with an architectural question: What would be genuinely useful, fun, and capable of running entirely local—respecting privacy with zero cloud dependencies? That is how the idea for GemmaLink was born.
Then came the engineering constraints. I knew the target user shouldn't have to deal with installing Docker, configuring Python environments, or setting up complex LLM studios. The app needed to be a single, lightweight, zero-dependency binary that could run anywhere.
The ideal architectural fit for this was Go—a language I only knew at a basic level.
Did I let that stop me? No. Because the language is just a detail. I used AI extensively to generate the Go syntax, acting as a tireless assistant. But the core architecture, the strict definition of requirements, the data layout, the testing, and the debugging? That was entirely mine. Without a structured engineering mindset setting the boundaries, the AI would have just generated generic Python scripts completely useless for my deployment constraints. Architecture won over syntax.
Conclusion: The Human Element of Mentoring
At the end of the day, passing down an "old school" mindset cannot be compressed into a rigid set of mathematical rules. Mentoring isn't something you can automate because it is built entirely on empathy, active listening, and human communication. A good mentor must first know how to listen, setting aside their own biases to understand how the student thinks.
My goal is to instill rigor and structure, but the curiosity, the drive, and the final decisions must always belong to the developer. It took me years of practice, experimentation, and trial to shape this mindset—and truth be told, even I struggle to fully rationalize it sometimes. But that is exactly how it should be. We are not all wired the same way, and each of us connects with others differently.
What do you think? Does this "learning to learn" approach make sense in your current AI-driven context? Let's discuss in the comments below.
I Bought a $100 Hat for $0 — Proving an AI Agent Was Human-Backed with World AgentKit
12 Jun 2026, 5:46 amA $100 hat showed up at my door in Kyoto. I paid nothing for it — not for the hat, not for shipping. $0.00.
It wasn't a coupon I found or a sale. The store, humanrequired.shop, only gives that discount to AI agents that can prove a real human is standing behind them. So I made my agent prove it, on-chain, with a zero-knowledge proof. The discount it got back was 100% off. Here's the whole path, because the design held together far better than I expected.
TL;DR
- World Foundation's AgentKit lets you prove on-chain that an agent is "human-backed."
- humanrequired.shop hands a human-only 100%-off discount to verified agents — once per World ID.
- The official Claude Code plugin (
worldcoin/agentkit-shopify-demo) ships the whole flow as skills. - Result: one "Human in the Loop" Hat, $100 → $0, shipping included, delivered to Japan.
Architecture
[Claude Code]
├─ plugin: agentkit-shopify
│ ├─ skill: shopify-agent-discount (SIWE signature → World discount API)
│ └─ skill: shopify-storefront (Shopify product JSON → cart URL)
│
├─ Agent Wallet (local Ethereum keypair)
│ └─ registered in AgentBook (on World Chain) = the human-backed proof
│
└─ Shopify (humanrequired.shop)
└─ /api/verify gate (Worldcoin's verification endpoint)
The key idea: you don't give the agent a World ID. A human delegates the agent's public key on-chain. The private key never leaves the machine.
How it works
Step 1 — Install the plugin
/plugin marketplace add worldcoin/agentkit-shopify-demo
/plugin install agentkit-shopify@worldcoin-agentkit
/reload-plugins
Step 2 — Generate an agent key
uv run --with eth-account python3 -c "from eth_account import Account; print(Account.create().key.hex())" > .agent-key
chmod 600 .agent-key
Check the wallet address:
uv run --with eth-account python3 -c "from eth_account import Account; print(Account.from_key(open('.agent-key').read().strip()).address)"
# => 0xC56A...
Step 3 — Register in AgentBook (the human-backed proof)
In a separate terminal (it shows a QR code):
npx @worldcoin/agentkit-cli register 0xC56A...
- Scan the QR in the World App.
- Your Orb-verified World ID generates a zero-knowledge proof.
- The AgentBook contract on World Chain writes
agent_address → human_nullifier. - Worldcoin's relayer covers the gas, so you pay nothing to register.
Step 4 — Call the discount API
get-coupon.py does four things:
- Signs a SIWE message (Sign-In with Ethereum, EIP-4361) with the key in
.agent-key. - Base64-encodes the signature into an
agentkit:HTTP header. - POSTs the product URL to
https://discount-app.worldcoin.org/api/verify. - The server checks AgentBook — if the agent maps to a registered human, it returns a discount code.
PRIVATE_KEY=$(cat .agent-key) ./get-coupon.py https://humanrequired.shop/products/human-in-the-loop-hat
# => WORLD-ID-ced1a8fe6682
Step 5 — Build the checkout URL
No special API needed on the Shopify side — the plain product JSON endpoint is enough.
curl -s "https://humanrequired.shop/products/human-in-the-loop-hat.json" | jq '.product.variants[0].id'
# => 46991516106914
Shopify's standard cart permalink finishes it:
https://humanrequired.shop/cart/<variant_id>:<qty>?discount=<code>
https://humanrequired.shop/cart/46991516106914:1?discount=WORLD-ID-ced1a8fe6682
What it cost
- The hat: $0.00
- Shipping to Kyoto: $0.00
- Gas to register on World Chain: $0.00 (Worldcoin's relayer paid it)
- Total out of pocket: $0.00
What broke
-
npx ...registerneeds its own terminal. It draws a QR code; running it inside Claude Code's Bash mangles the output. Run it in a real terminal. - Don't do a test run. The discount code is derived deterministically from your nullifier hash, so "let me just call it once to see" spends your one real code. I held off on hitting the API until the checkout URL was fully assembled.
-
Strip query params off the product URL. A trailing
?variant=123can make the discount API treat it as a different product. -
/reload-pluginsdid nothing in my setup. The skills are just bash scripts under~/.claude/plugins/marketplaces/worldcoin-agentkit/skills/, so I ran them by hand and the flow still completed.
Why this matters
A few design choices stood out, and they're the reason I bothered writing this down:
-
The discount code appears to be deterministic. The tail of
WORLD-ID-ced1a8fe6682matched the tail of the nullifier hash from registration (0x2fd8701b...a8fe6682). I can't see the server, but that match strongly suggests the code is derived from the nullifier rather than from a stored counter or random value — which would mean "one human, one code" is enforced cryptographically, not by a database row. -
The private key never goes over the wire. Auth is a SIWE signature; the server does
ecrecoverto get the signer address, then looks up the human link in AgentBook. -
The MCP turned out to be unnecessary. The plugin registers a Shopify Storefront MCP, but the
shopify-storefrontskill just curls Shopify's public product JSON. Easy to miss, and the right call.
I spend a lot of time watching the supplier side of agentic commerce — sites that gate AI traffic. This was the rare chance to be the consumer passing through one of those gates, and the gate's logic is positive: not "block AI," but "let human-backed agents through." Claude Code + plugin + external MCP + a local private key + a ZK proof + an on-chain registry, all meshing cleanly. The hat is the souvenir.
I'm KAMO, a developer in Kyoto. I write implementation logs — working code, real costs, what broke.
- Get every log: https://bykamo.substack.com
- Portfolio: bykamo.dev
milvuslite-kit configuration over code for vector search and rag workflows
12 Jun 2026, 5:43 amBuilt a small framework called MilvusLite Kit to make vector search and RAG workflows more configuration-driven and less code-heavy.
Instead of writing boilerplate for embeddings, vector stores, and retrieval setup, you can define everything through configuration and focus on building your application.
In this post, I walk through the idea, architecture, and how it simplifies local development with Milvus Lite.
Feedback and contributions are welcome.
Building GoRelay: A Durable Task Queue for Go with Zero Infrastructure
12 Jun 2026, 5:42 amEvery Go developer has written this line:
go sendEmail(user)
It works perfectly... until it doesn't. Your server crashes, the task disappears forever, and your user never gets that welcome email.
I kept building the same solution: a task queue with retries, persistence, and monitoring. But every time, I had to set up Redis, configure workers, and write boilerplate code.
So I built GoRelay - a durable task queue that works out of the box.
What is GoRelay?
GoRelay turns any Go function into a crash-resistant, retryable background job.
The API is simple:
r.Register("email.send", SendEmail)
r.Enqueue("email.send", EmailPayload{To: "user@example.com"})
What makes it different:
- Setup: go get and done (no Redis required)
- Default storage: SQLite (file-based, zero config)
- Dashboard: Built-in, no separate setup
- Retries: Automatic with exponential backoff
- Backends: SQLite, PostgreSQL, Redis - same code works everywhere
The Journey to v1.0
Building an open source library taught me more than I expected.
First, I started with the API first. I wrote example code showing how I WANTED it to work, then made it work that way.
Second, storage abstraction is critical. GoRelay supports SQLite, PostgreSQL, and Redis with the same interface. Start with SQLite on your laptop, deploy to PostgreSQL in production, scale to Redis for high throughput - no code changes needed.
Third, the dashboard changed everything. I almost skipped it. Big mistake. Being able to see tasks in real-time - their status, history, and errors - is what makes GoRelay trustworthy for production use.
How It Works Under the Hood
Lock-Free Ring Buffer
GoRelay uses a lock-free ring buffer for task passing between producers and consumers. This means no mutex contention and no goroutine blocking during normal operation.
Priority Queues
Tasks are processed in priority order: High, Normal, Low. This ensures critical payments never wait behind analytics jobs.
Automatic Retries
Failed tasks retry with exponential backoff. The delay increases with each attempt: 1s, 2s, 4s, 8s, up to 1 hour. Random jitter prevents thundering herd problems when services recover.
Multiple Storage Backends
Each backend is optimized for its strength:
- SQLite: Embedded, zero config, perfect for side projects and internal tools
- PostgreSQL: ACID compliant, supports multiple workers, ideal for production SaaS
- Redis: High throughput, great for existing Redis shops
Real-World Use Cases
GoRelay is being used for:
- Welcome emails after user signup
- PDF report generation
- SMS notifications
- Data processing pipelines
- Scheduled maintenance tasks
What I Learned Building Open Source
Documentation is code. I spent 40% of my time on README, examples, and godoc. Worth it. Users judge your library by its documentation first.
Tests build confidence. Writing tests caught bugs I didn't know I had. The race detector (go test -race) is your best friend.
Release early, release often. v1.0 doesn't need every feature. Ship something useful, then iterate.
What's Next for GoRelay
- v1.1: Webhooks and Outbox pattern for transactional consistency
- v1.2: Idempotency keys to prevent duplicate processing
- v2.0: Distributed tracing and Prometheus metrics
Try GoRelay Today
Install:
go get github.com/amitstephen-dev/gorelay@v1.0.0
Complete working example:
package main
import (
"fmt"
"github.com/amitstephen-dev/gorelay"
)
type EmailPayload struct {
To string
Body string
}
func SendEmail(payload interface{}) error {
p := payload.(*EmailPayload)
fmt.Printf("Sending to: %s\n", p.To)
return nil
}
func main() {
r := gorelay.New()
r.Register("email.send", SendEmail, &EmailPayload{})
r.EnableDashboard(":8080")
r.Start()
r.Enqueue("email.send", &EmailPayload{
To: "user@example.com",
Body: "Welcome to GoRelay!",
})
select {}
}
Open http://localhost:8080 to see the dashboard with real-time task monitoring.
How Fast Is It?
Instead of giving you marketing numbers, I encourage you to benchmark GoRelay on your own hardware with your own workload patterns.
The architecture is designed for efficiency:
- Lock-free ring buffer for zero contention
- Zero-copy JSON for small payloads
- Batch writes to reduce database round trips
Actual throughput depends on your storage backend:
- SQLite: Great for development and moderate workloads
- PostgreSQL: Production-ready with strong consistency
- Redis: Highest throughput for high-volume workloads
Memory usage is typically under 50MB for most workloads.
Links
- GitHub: github.com/amitstephen-dev/gorelay
- Documentation: pkg.go.dev/github.com/amitstephen-dev/gorelay
- Report Issues: github.com/amitstephen-dev/gorelay/issues
Closing Thoughts
Building GoRelay taught me that simple tools can solve complex problems. You don't always need Kubernetes or microservices. Sometimes, a well-designed library with a SQLite backend is all you need.
The best part? You can start with zero infrastructure. No Redis. No Postgres. Just go get and go run.
If GoRelay saves you time or solves a problem, star the repo on GitHub - it helps other developers find it.
Happy coding!
Built with love for the Go community
I published two IEEE Access papers as an undergrad, one on IoT security, one on cancer detection
12 Jun 2026, 5:42 amI'm a 6th semester CS student at COMSATS University Islamabad. Over the past few months I've been doing deep learning research alongside my coursework, and we've submitted two papers to IEEE Access. Here's what we built and what I learned.
Paper 1 — IoT Intrusion Detection
IoT devices get attacked constantly. The problem with most deep learning IDS research is that models are validated on one dataset and never tested anywhere else — so you don't actually know if they generalize.
We took the CNN-DNN architecture from Nazari et al. (validated on Kitsune) and re-evaluated it on CICIDS-2017 — 2.8 million network flow records, 12 attack classes. It didn't just hold up, it improved: from 98.47% to 99.50% accuracy. That's the generalizability proof the original paper left open.
We also benchmarked a Transformer. Theoretically appealing for traffic classification — self-attention should model complex feature dependencies well. In practice on tabular network flow data, it landed at 96.98% and struggled on specific classes. Not bad, but behind CNN-DNN, and consistent with what the literature says about vanilla transformers on tabular data.
The finding I'm most proud of: a Lightweight model with only 17,772 parameters hit 97.55% accuracy — 21× fewer parameters than CNN-DNN — without needing GPU acceleration. That's actually deployable on constrained IoT edge hardware.
SMOTE applied only to the training set took CNN-DNN from 92.35% to 99.50%. Never touch the test set with synthetic samples.
Paper 2 — Breast Cancer Grading
Binary benign/malignant histopathology classification is a mostly solved problem — models hit 98%+ routinely. But a binary label isn't clinically useful. A surgeon needs to know if it's ductal carcinoma or lobular carcinoma because the treatment differs.
We extended the CBAM-VGGNet framework from Ijaz et al. (which did binary classification and explicitly listed multi-class grading as future work) to 8-class subtype grading on the BreakHis dataset. Replaced VGGNet with ResNet50V2, evaluated across all four magnification factors combined rather than individual subsets, and added GradCAM explainability.
Final result: 82.81% on the 8-class task, 96.27% binary accuracy. The GradCAM heatmaps attend to anatomically correct regions — nuclear pleomorphism for ductal carcinoma, organized acinar patterns for adenosis. That alignment with pathological criteria is what makes a model credible in a clinical setting, not just accurate.
Lobular carcinoma (F1: 0.67) and papillary carcinoma (F1: 0.74) were the hardest — morphological overlap with other malignant subtypes is a real challenge, not just a model failure.
What doing research as an undergrad actually taught me
Honest reporting matters more than clean numbers. Both papers include a limitations section that explicitly calls out where the models fail and why. Web Attack XSS got near-zero F1 across all three models in Paper 1 — we reported it, explained the structural reason (26 real test samples), and didn't hide it.
Two-phase fine-tuning in Paper 2 — frozen backbone first, then selective unfreezing — is not just a trick. Without it, fine-tuning a deep pretrained network on a small medical dataset will catastrophically forget useful features before the head stabilizes.
And SMOTE is not magic. It helped massively, but it can't compensate for insufficient real test samples. Know what it can and can't do.
Both papers are under review at IEEE Access. I'll update this post when they publish.
I'm Ahmad Mustafa, a Full Stack Developer and deep learning researcher based in Islamabad, Pakistan. I build AI-powered products and publish work in IoT security and medical imaging.
🔗 Research Repository: https://github.com/ahmadmustafa02/iot-malware-detection-research
📄 Paper 1 — IoT Intrusion Detection: Under review · IEEE Access
📄 Paper 2 — Breast Cancer Grading: Under review · IEEE Access
Website: https://ahmadmustafa.me/
LinkedIn: https://www.linkedin.com/in/ahmadmustafa01/
Why 60% of Enterprises Are Shipping Untested Code in 2026 (And How Agentic QA Fixes It)
12 Jun 2026, 5:36 amThe 2026 Agentic Coding Trends Report buried a stat that should be on every engineering leader's radar: 60% of enterprises are shipping untested code as AI accelerates software development.
Let that sink in. We gave developers a rocket ship — and forgot to put a seatbelt on it.
What Actually Happened
In 2024–2025, AI coding copilots went mainstream. By mid-2026, 85% of developers use AI tools daily, and 46% of all production code is now AI-generated (Modall, 2026).
Velocity improved dramatically. Ship timelines compressed. Product teams celebrated.
But the testing layer didn't scale with the build layer. Here's the problem in concrete terms:
- The Velocity Gap: A developer using Claude Code or Cursor can produce working feature code in 40 minutes that previously took a day.
- The Human Bottleneck: The QA cycle for that same feature — regression setup, test scripting, execution, defect triage — still runs on human timelines.
- Code Bloat: Code duplication is up 4x with AI-generated code, meaning test surface area is larger, not smaller.
- Resource Stagnation: Most teams didn't hire more QA engineers; they hired more AI coding tools.
The result: a growing quality debt hiding beneath fast-moving velocity metrics.
Why Traditional Test Automation Doesn't Save You
You might think: "We have Selenium/Playwright automation — we're covered."
Not quite. Traditional test automation has a maintenance problem. As AI-generated code ships faster, scripts break faster. A test suite that was stable for three sprints can break across 30 files in a single AI-accelerated week.
The Gartner 2026 Software Testing Predictions note that teams relying purely on script-based automation are spending 40–60% of QA time on test maintenance rather than coverage expansion. That ratio inverts the purpose of automation entirely.
What Agentic QA Actually Does (Non-Hype Version)
Agentic QA systems aren't just "AI that runs tests." The distinction matters:
- Traditional test automation: Human writes script $\rightarrow$ script runs $\rightarrow$ human fixes broken script.
- Agentic QA: Agent reads requirements + code changes $\rightarrow$ agent generates tests $\rightarrow$ agent runs tests $\rightarrow$ agent heals broken tests $\rightarrow$ agent reports coverage gaps $\rightarrow$ human reviews outcomes.
The key shift: the agent operates on goals ("maintain 85% coverage of checkout flow") rather than scripts ("run these 47 test cases").
Practically, this means:
Input: Plain-English acceptance criteria: "Users should be able to complete checkout with 3 or fewer clicks"
Output (Agentic QA Agent):
- Generated: 12 test cases covering happy path + edge cases
- Discovered: 2 untested code paths in payment validation
- Coverage delta: +8.3% on checkout module
- Time: 4 minutes
Teams adopting agentic QA are reporting 5–10x test coverage growth at the same QA headcount because the authoring bottleneck moves from human to agent (Tricentis, 2026).
A Real-World Implementation Pattern
At Ailoitte, we've built agentic QA into the core of our delivery methodology across 300+ shipped products. The pattern we use across healthcare, fintech, and e-commerce clients follows these key steps:
1. Requirement Ingestion
Acceptance criteria are fed directly to the QA agent at ticket creation, not at the end of the sprint.
2. Parallel Test Generation
While developers build, the QA agent drafts test cases. By the time the code is ready for review, test cases are already staged.
3. Continuous Coverage Analysis
Every commit triggers a coverage delta report. Gaps are surfaced directly in the PR, not in production.
4. Self-Healing Scripts
When a UI change breaks a selector, the agent re-discovers the element rather than failing silently or blocking the CI/CD pipeline.
5. Human-in-the-Loop for Critical Paths
Complex user flows (e.g., payment processing, medical data entry) get dedicated human QA review. The agent handles breadth; humans handle depth.
This pipeline is one reason Ailoitte ships in 38 days on average vs. the industry average of 120+ days — without sacrificing quality. You can read more about our Agentic QA Pipeline and how it integrates with our broader AI Velocity Pod methodology.
How to Start (Practical Steps for Engineering Teams)
You don't need to rip out your existing test stack. Follow this incremental approach instead:
- Instrument your coverage baseline: You can't improve what you don't measure. Tools like Codecov combined with custom dashboards work well.
- Pick one agentic QA tool for one module: Katalon, Tricentis, or Testim all have agentic modes worth piloting.
- Feed it requirements, not scripts: The paradigm shift is in the inputs. Stop writing "do X, expect Y." Start writing "this module must handle Z."
- Measure coverage growth per sprint: Track this metric alongside velocity. If velocity goes up and coverage goes down, you have a problem surfacing.
- Graduate to full pipeline integration: Scale up over 2–3 sprints as the team builds confidence in agent outputs.
The Bigger Picture
The 60% stat isn't a QA failure. It's an organizational mismatch — velocity tooling scaled, but quality tooling didn't. The organizations closing this gap fastest are the ones treating agentic QA as an infrastructure investment, not a QA team problem.
In 2026, shipping fast is table stakes. Shipping fast and clean is the actual competitive advantage.
Over to You
What does your current test coverage look like relative to your AI-generated code percentage? Drop your setup in the comments — genuinely curious where teams are.
Ailoitte is an AI-native product engineering company that ships fixed-price, outcome-based software using AI Velocity Pods. We've shipped 300+ products across 21 countries. Explore our Agentic QA Pipeline
I built an AI code review GitHub App that tracks your team's mistake patterns, here's how
12 Jun 2026, 5:36 amMost AI code review tools tell you what's wrong in a PR. CodePulse tells you what your team keeps getting wrong, week after week.
I built CodePulse as a GitHub App that does two things regular code review bots don't: it stores every finding per developer over time, and sends a personalized weekly digest summarizing each person's recurring issue categories. The idea is simple — if you're writing the same type of bug every week, you should know about it.
How it works
Install the GitHub App on your repos. Every time a PR is opened, CodePulse fetches the diff, runs a two-pass AI analysis using Groq's Llama 3.3 70B with structured tool-calling, and posts inline review comments pinned to exact lines with severity labels — Critical, High, Medium, Low.
Every finding gets stored against the developer who wrote it, the repo, and the PR. This is what makes CodePulse different from a one-shot reviewer — it builds a history.
Every Sunday, developers who opt in get a personalized email digest aggregating their issue categories from the past week. Not a wall of comments — a summary of patterns.
The Stack
Backend runs on Node.js, Express, TypeScript with Prisma ORM and Neon PostgreSQL on Azure. Frontend is React with TanStack Router on Vercel. Weekly digests go out via Resend, triggered by a GitHub Actions cron job.
The trickiest part was the two-pass analysis — a file triage pass first to skip lockfiles, minified assets, and generated files, then a chunked deep review pass that returns typed JSON per issue via structured tool-calling. Getting Groq to return consistent structured output at speed was the core engineering challenge.
What I learned
Webhook reliability is everything in a GitHub App. HMAC-SHA256 verification, idempotent processing, and logging every delivery from GitHub's Advanced tab saved me hours of debugging. If you're building on GitHub webhooks, treat the Recent Deliveries panel as your best friend.
Multi-tenant isolation also required more upfront schema design than I expected, scoping every query by GitHub App installation ID from day one is non-negotiable.
Try it
Live at getcodepulse.vercel.app — install the GitHub App on any repo and open a PR. Inline comments appear within 1–3 minutes.
Source on GitHub: https://github.com/ahmadmustafa02/CodePulse
I'm Ahmad Mustafa, a Full Stack Developer based in Islamabad, Pakistan. Building AI-powered products and publishing research in deep learning for IoT security and medical imaging.
LinkedIn: https://www.linkedin.com/in/ahmadmustafa01/
Website: https://ahmadmustafa.me/
Github:
Day 23 of Leaning MERN Stack
12 Jun 2026, 5:16 amHello Dev Community! 👋
It is officially Day 23 of my journey to master the MERN stack! Today, I wrapped up Lecture 8 of Apna College's JavaScript playlist with Shradha Didi, focusing on the ultimate way to handle user interactions: Event Listeners and the Event Object.
Yesterday, I noticed that standard event handlers like btn.onclick overwrite previous code. Today, I unlocked the professional solution to that problem.
🧠 Key Learnings From JS Lecture 8 (Part 2)
Here is the exact logical breakdown of the concepts I mastered today:
1. The Event Object (e)
I learned that whenever an event occurs, the browser automatically passes a special object full of data into our function—usually named e or evt.
-
e.type: Tells us exactly what event happened (e.g.,"click"or"mouseover"). -
e.target: Points to the exact HTML element that triggered the action. This is incredibly useful for tracking down user behavior dynamically!
2. Event Listeners (addEventListener)
This is the professional standard for web engineering. By using element.addEventListener("event", callback), we can listen for actions cleanly. The best part? We can attach multiple event listeners to the exact same element without anything getting overwritten!
3. Removing Listeners (removeEventListener)
I learned that we can also stop listening to actions using removeEventListener. A crucial rule Shradha Didi shared is that to remove a listener, the callback function must be stored in a variable so that its exact memory reference can be passed in.
🛠️ What I Actually Built (The Big Highlight!)
Today, I built a real-world, industry-standard feature: A Dark Mode / Light Mode Theme Toggle.
- Created a button on the webpage.
- Attached an
addEventListener("click", ...)to it. - Tracked the current state inside a variable (
currMode). - Styled separate
.darkand.lightbackground/text rules in CSS. - Used
.classList.add()and.classList.remove()inside the script to smoothly swap the entire webpage appearance back and forth based on the user's click.
Watching the entire UI shift from a clean white layout to a sleek dark theme with a single click felt absolutely incredible!
🎯 My Goal for Tomorrow (Day 24 / Project Day)
I have successfully wrapped up all the foundational DOM and Event lectures from Apna College. Tomorrow, it's time to test my memory and logic layout:
- Building a full standalone mini-project using HTML, CSS, and vanilla JavaScript events.
- Practicing clean code modularity.
💬 Let's Connect!
To the Apna College community: Did you implement your theme toggle using simple inline styling changes or by toggling CSS classes via classList? To seniors: Do you still use e.target heavily when handling complex forms?
My complete theme toggle repository is live on GitHub!
[Links in the Comments]
Keep learning, keep building! 🚀
Why Abstractions Leak
12 Jun 2026, 5:16 amOne of the most dangerous moments in a developer's career is when an abstraction works perfectly.
Not because success is bad.
But because perfect abstractions create an illusion.
The illusion is:
I don't need to know what's underneath.
For a while, that's true.
Then production happens.
Then scale happens.
Then edge cases happen.
Then reality happens.
And suddenly:
The abstraction leaks.
This idea is so common in software engineering that it has a name:
The Law of Leaky Abstractions
And once you see it, you'll notice it everywhere.
What Is An Abstraction?
An abstraction hides complexity.
Example:
fetch("/users")
Looks simple.
Behind that line:
DNS Lookup
TCP Connection
TLS Handshake
HTTP Request
Network Routing
Server Processing
Serialization
Response Parsing
Thousands of things happen.
The abstraction hides them.
That's the point.
Why We Need Abstractions
Imagine writing:
openSocket()
performTLSHandshake()
buildHTTPRequest()
serializeHeaders()
sendRequest()
readResponse()
parseJSON()
every time.
Nobody wants that.
Abstractions make software possible.
Without them:
Modern Software Would Not Exist
The Problem
The abstraction hides complexity.
It does not remove complexity.
That distinction matters.
Because eventually:
fetch("/users")
fails.
And suddenly:
You Need To Understand
The Things It Was Hiding
Example #1: ORMs
Developers love ORMs.
Example:
const users =
await User.findAll()
Looks beautiful.
Looks simple.
Looks harmless.
Then somebody adds:
user.posts
inside a loop.
Suddenly:
100 Users
100 Queries
Database Meltdown
The famous:
N + 1 Query Problem
appears.
The ORM abstraction leaked.
Example #2: React
React hides DOM manipulation.
Instead of:
document.createElement(...)
we write:
<UserCard />
Wonderful.
Until:
5000 Components
Frequent Updates
Slow Rendering
Now you need to understand:
Reconciliation
Memoization
Reference Equality
Rendering Lifecycle
The abstraction leaked.
Example #3: Promise.all()
Looks simple.
await Promise.all(
requests
)
Feels magical.
Until:
5000 Requests
hit production.
Now:
Rate Limits
Memory Pressure
Connection Pools
Timeouts
suddenly matter.
Again:
The abstraction leaked.
Example #4: Kubernetes
Developers often say:
Kubernetes Makes Deployment Easy
Until something breaks.
Then:
Pods
Services
Ingress
DNS
Networking
Volumes
Secrets
Scheduling
become your problem.
The abstraction leaked.
Example #5: Functional Programming
Even FP abstractions leak.
Consider:
users
.map(...)
.filter(...)
.map(...)
Looks elegant.
Then:
1 Million Records
arrive.
Now:
Allocations
Garbage Collection
Intermediate Arrays
matter.
Suddenly:
You Need To Understand
The Implementation
The abstraction leaked.
The Reduce Example
Earlier in this series we discussed:
users.reduce(
(acc, user) => ({
...acc,
[user.id]: user
}),
{}
)
Looks elegant.
Looks immutable.
Looks functional.
At scale:
O(n²)
The abstraction leaked.
The implementation became important.
Why Leaks Are Inevitable
Many developers think:
Good Abstractions
Never Leak
This is impossible.
Because reality is more complex than any abstraction.
Always.
Eventually:
Performance
Memory
Concurrency
Networking
Storage
Scale
find a way through.
The Database Example
You can use:
User.findAll()
for years.
Then one day:
Database CPU 100%
Now suddenly:
SELECT *
FROM users
matters.
The SQL was always there.
The ORM simply hid it.
The Cloud Example
Many developers think:
Serverless Means
No Servers
It doesn't.
It means:
Someone Else's Servers
Eventually:
Cold Starts
Execution Limits
Concurrency Limits
appear.
The abstraction leaks.
Why Senior Engineers Feel Different
Junior engineers often trust abstractions.
Senior engineers respect abstractions.
Those are not the same thing.
A senior engineer sees:
fetch(...)
and mentally understands:
Network
Latency
Retries
Timeouts
Failures
even when they aren't visible.
That's experience.
The Best Developers Learn Both Layers
Bad approach:
Only Learn Frameworks
Also bad:
Only Learn Internals
The best developers learn:
Abstraction
+
Implementation
Both matter.
Real World Example: API Studio
Suppose we're building an API Studio.
We expose:
Send Request
as a button.
Simple.
Behind the scenes:
DNS
TLS
Headers
Body Serialization
Compression
Retries
Timeouts
Certificates
Users shouldn't see those details.
But we must understand them.
Because eventually:
A Bug Will Live There
Real World Example: Git
Git feels simple.
git commit
Until:
Merge Conflicts
Detached HEAD
Rebase Failures
Corrupted Refs
appear.
Then:
Objects
Trees
Blobs
Refs
Packfiles
suddenly matter.
The abstraction leaked.
The Hidden Connection To This Entire Series
Reduce leaked.
We learned Transducers.
Transducers leaked.
We learned composition.
RxJS leaked.
We learned streams.
Event Sourcing leaked.
We learned reducers.
Every article in this series is actually a story about abstractions leaking.
And that's not a bad thing.
That's how deeper understanding develops.
Pros Of Abstractions
1. Faster Development
Less code.
2. Better Productivity
Focus on business logic.
3. Lower Cognitive Load
Hide complexity.
4. Better Reusability
Shared solutions.
5. Better Collaboration
Common interfaces.
Cons Of Abstractions
1. Performance Surprises
The implementation is hidden.
2. Debugging Complexity
Problems occur below the abstraction.
3. Knowledge Gaps
Developers stop learning fundamentals.
4. Vendor Lock-In
Sometimes abstractions become cages.
5. Leaks
Eventually reality breaks through.
Always.
The Real Lesson
The biggest mistake developers make is assuming abstractions remove complexity.
They don't.
They move complexity.
That's a huge difference.
The complexity still exists.
It's simply hidden until something goes wrong.
And when that moment arrives, the engineers who understand both layers:
The Abstraction
and
The Implementation
are the ones who solve the problem.
That's why great engineers never stop at the API.
Eventually they look underneath.
Because every abstraction leaks.
And that's where the real learning begins.
What's Next?
In the next article we'll discuss:
The Two Hard Things In Computer Science
Because one famous joke contains an uncomfortable amount of truth:
There are only two hard things
in Computer Science:
Cache Invalidation
Naming Things
and Off-by-One Errors
And surprisingly, that joke explains a huge amount of software engineering.
About The Author
Hi, I'm Amrish Khan.
I enjoy building developer tools, exploring software architecture, and writing about the deeper ideas behind everyday programming concepts.
I'm also building Aruvix — a growing ecosystem of local-first developer tools designed to process data directly in the browser without unnecessary uploads.
Here's a detailed blog on Aruvix:
https://dev.to/amrishkhan05/aruvix-the-ultimate-offline-first-developer-toolkit-e0i
You can follow my work and thoughts here:
Portfolio:
https://www.amrishkhan.dev
LinkedIn:
https://www.linkedin.com/in/amrishkhan
GitHub:
https://www.github.com/amrishkhan05
If you enjoyed this article, consider following for more deep dives into JavaScript, architecture, local-first software, and performance engineering.
