2025-11-12 17:19:32
It was one of those Friday nights that make you feel invincible.
Every test passed. Every coverage metric gleamed above 90%.
The CI pipeline was all green — the kind of green that makes you think, “Yeah, we nailed it.”
As a senior engineer working in Trade Finance, where a single API call can move millions, that confidence felt well earned.
I had done my homework:
I pushed to production, shut my laptop, and packed for a weekend trek up Agasthyakoodam.
Confidence? Sky-high. The kind that only comes from seeing everything green.
Saturday, 8:07 AM.
Fresh mountain air. Backpack on. First step up the trail.
📱 Buzz.
“Production is down. URGENT.”
I froze.
No signal. No backup. Just me, a laptop, and a mountain.
I climbed faster — not for the view, but for network bars.
By the time I reached 6,000 feet, I had a faint 4G signal.
Sitting on a rock, I opened my laptop to find the logs that would ruin my morning.
The logs told a simple, painful story.
An API call failed — triggering a domino of retries, timeouts, and broken workflows.
The cause? A missing trailing slash in an environment variable.
Here’s what went wrong:
https://api.example.com/), endpoint started clean (status). ✅https://api.example.com), endpoint began without one (status). ❌When the two joined, the resulting URL was malformed.
The urljoin() call, which had behaved perfectly in staging, broke in production.
A single missing slash — and a multi-million-dollar workflow came to a halt.
My 94% test coverage didn’t see it coming.
That was the day I learned:
Test coverage ≠ confidence coverage.
Unit tests make you feel safe.
Integration tests make you feel thorough.
But production? It humbles you.
Because:
The irony? The tests weren’t wrong — they did their job.
They just didn’t prepare me for reality.
In Trade Finance, every integration layer is mission-critical.
Dozens of systems — banks, partners, repositories, regulators — all stitched together through APIs and assumptions.
A single malformed URL isn’t just a 500 error.
It’s a delay in document exchange.
A payment that doesn’t clear.
A customer commitment that slips.
When one link fails, the entire chain rattles.
And your “all green” test suite? It won’t catch it.
That morning on the mountain changed my mental model.
Forget chasing 100% code coverage — start measuring confidence coverage.
Confidence coverage is the degree of assurance that your system will behave correctly under real conditions, not just ideal ones.
It’s built through:
When you think in terms of confidence coverage, the question shifts from:
“How many lines are tested?”
to
“How sure am I that this won’t blow up in production?”
Testing isn’t about perfection — it’s about preparation.
Your tests won’t catch every failure, but they’ll teach you how to handle them gracefully.
Environment parity matters more than coverage percentages.
A missing slash, a timeout, a wrong env var — these break real systems.
Integrate early. Integrate often.
The earlier you touch real systems, the fewer weekend calls you’ll get.
Monitor everything.
Tests prevent some fires. Monitoring tells you when one’s already burning.
Confidence is good. Humility is better.
Every “impossible” bug reminds you: software at scale isn’t deterministic — it’s probabilistic with flair.
I fixed the issue right there on the mountain — half a battery, one patch, and a heavy dose of humility later.
The system came back up.
The trek continued.
The view at the top? Worth every step.
But something changed in how I saw engineering.
2025-11-12 17:18:17
We live in the so-called “Information “Age”—when nearly every fact, figure, or opinion is just a click away. Search engines answer questions in seconds, and AI tools can summarize textbooks in minutes. Yet, despite this digital abundance, the difference between information, knowledge, and wisdom has never been more crucial—or more misunderstood.
“Knowing information doesn’t mean acquiring knowledge. And knowledge without wisdom is like having tools without knowing how or when to use them.”
In today’s hyper-connected world, many confuse access to information with understanding. A quick Google search or an AI-generated answer might feel like knowledge, but it’s often shallow, decontextualized, and detached from lived experience.
True wisdom is earned. It emerges slowly, often painfully, through lived experiences, failures, and reflection. It involves context, nuance, and the ability to make sound judgments—things that no amount of raw data or AI summaries can replace.
Information is data—disconnected facts.
Knowledge is understanding—when those facts are connected and made sense of.
Wisdom is application—when that knowledge is used with judgment, timing, and empathy.
Immanuel Kant explained the process of acquiring real knowledge with precision:
“Percepts without concepts are blind; concepts without percepts are empty.”
In simple terms, experience without understanding leads nowhere, and understanding without experience is hollow.
Lao Tzu, the ancient Chinese philosopher, captured the essence of true learning:
“Not knowing is true knowledge. Presuming to know is a disease.” — Tao Te Ching
In other words, the moment we assume we know, we block the path to deeper learning. Accepting “not knowing” is the key to real growth. It opens the mind to curiosity, humility, and lifelong learning. In contrast, the overuse of AI and the internet for instant answers can reinforce superficial understanding and sometimes even arrogance.
The rampant use of AI and the internet for “quick answers” has led to:
Loss of depth in learning—memorization without true understanding
Overconfidence in half-truths—a false sense of expertise
Dependency on automation—weakening critical thinking and original thought
Neglect of experience—forgetting that some things can only be learned by doing
Whether you’re a student, a doctor, a writer, or a decision-maker, real understanding means going beyond the surface. It means asking “why,” grappling with uncertainty, challenging assumptions, and being okay with not knowing.
Slow down the learning process. Don’t just consume—reflect.
Seek experiences, not just answers. Learn by doing, failing, and questioning.
Use AI and internet tools as companions, not crutches.
Respect the unknown. Humility is the foundation of wisdom.
Learn from the wise. Elders, mentors, and hands-on practitioners carry insights no algorithm can replicate.
In an age of instant answers, true learners are those who embrace the long road—the one paved with curiosity, experience, and reflection. Let’s not mistake speed for depth or data for truth. Because ultimately, wisdom is not downloaded—it is lived.
As Lao Tzu reminds us, accepting not knowing is not a weakness—it is the beginning of true knowledge.
2025-11-12 17:13:54
Series Navigation: Part 1: Building the Agent | Part 2: Connect to Frontend (You are here)
In Part 1, we built a fully functional AI summarizer agent that can fetch content from URLs and generate intelligent summaries.
In this follow-up, you’ll learn how to connect that agent to a Next.js frontend using the Mastra Client, bringing your summarizer to life with a simple web interface.
Here’s what the final result looks like Ai-summarizer-agent.com:
Let's start by setting up a Next.js project that integrates with our Mastra agent.
If you're starting fresh, create a new Next.js app:
npx create-next-app@latest ai-summarizer-app
cd ai-summarizer-app
When prompted, select:
src/ directory: Yes
The Mastra Client allows your frontend to communicate with your agents:
npm install @mastra/core
Copy your agent code from Part 1:
src/mastra folder from Part 1src/mastra
.env file with the Gemini API keyYour project structure should now look like:
ai-summarizer-app/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ └── summarize/
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components/
│ │ └── SummarizerUI.tsx
│ ├── mastra/
│ │ ├── agents/
│ │ │ └── summarizer.ts
│ │ ├── tools/
│ │ │ └── web-scraper.ts
│ │ └── index.ts
├── .env
├── package.json
└── tsconfig.json
Next.js API routes will act as the bridge between your frontend and the Mastra agent.
Create a new file at src/app/api/summarize/route.ts:
import { summarizerAgent } from '@/src/mastra/agents/summarizer';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const { input } = await request.json();
if (!input || typeof input !== 'string') {
return NextResponse.json(
{ error: 'Input is required', success: false },
{ status: 400 }
);
}
const result = await summarizerAgent.generate(input, {
maxSteps: 5,
});
return NextResponse.json({
text: result.text,
success: true,
});
} catch (error) {
return NextResponse.json(
{
error: error instanceof Error ? error.message : 'Failed',
success: false,
},
{ status: 500 }
);
}
}
What's happening here:
input fieldsummarizerAgent with the input and an optional parameter called maxSteps (maxSteps defines how many “reasoning steps” the agent can take while generating the summary)Now let's create an interactive UI for our summarizer.
Create a new file at src/components/SummarizerUI.tsx:
'use client';
import { useState } from 'react';
type InputMode = 'url' | 'text';
export default function SummarizerUI() {
const [input, setInput] = useState('');
const [summary, setSummary] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [mode, setMode] = useState<InputMode>('url');
const handleSummarize = async () => {
if (!input.trim()) {
setError('Please enter a URL or text to summarize');
return;
}
setLoading(true);
setError('');
setSummary('');
try {
const response = await fetch('/api/summarize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ input }),
});
if (!response.ok) {
throw new Error('Failed to generate summary');
}
const data = await response.json();
if (data.success) {
setSummary(data.text);
} else {
setError(data.error || 'Failed to generate summary');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong');
} finally {
setLoading(false);
}
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey && mode === 'url') {
e.preventDefault();
handleSummarize();
}
};
return (
<div className="max-w-4xl mx-auto p-6 space-y-6">
{/* Header */}
<div className="text-center space-y-2">
<h1 className="text-4xl font-bold text-gray-900">AI Summarizer Agent</h1>
</div>
{/* Mode Selector */}
<div className="flex gap-2 justify-center">
<button
onClick={() => setMode('url')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
mode === 'url'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
URL
</button>
<button
onClick={() => setMode('text')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
mode === 'text'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
Text
</button>
</div>
{/* Input Area */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
{mode === 'url' ? 'Enter URL' : 'Enter Text'}
</label>
{mode === 'url' ? (
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="https://example.com/article"
className="w-full px-4 py-3 border text-black border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
disabled={loading}
/>
) : (
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Paste your text here..."
rows={6}
className="w-full px-4 py-3 border text-black border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
disabled={loading}
/>
)}
</div>
{/* Submit Button */}
<button
onClick={handleSummarize}
disabled={loading || !input.trim()}
className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{loading ? (
<span className="flex items-center justify-center gap-2">
<svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
fill="none"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
Generating Summary...
</span>
) : (
'Summarize'
)}
</button>
{/* Error Message */}
{error && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800 text-sm">
<span className="font-medium">Error:</span> {error}
</p>
</div>
)}
{/* Summary Output */}
{summary && (
<div className="space-y-3">
<h2 className="text-xl font-semibold text-gray-900">Summary</h2>
<div className="p-6 bg-gradient-to-br from-blue-50 to-indigo-50 border border-blue-200 rounded-lg">
<div
className="prose prose-sm max-w-none text-gray-800"
dangerouslySetInnerHTML={{
__html: summary.replace(
/\*\*(.*?)\*\*/g,
'<strong>$1</strong>'
),
}}
/>
</div>
</div>
)}
{summary && (
<button
onClick={() => {
setSummary('');
setInput('');
setError('');
}}
className="text-sm text-gray-600 hover:text-gray-900 underline"
>
Clear and start over
</button>
)}
</div>
);
}
What's happening here:
/api/summarize with the user's input and updates the UI based on the responseUpdate src/app/page.tsx to use our new component:
import SummarizerUI from '@/components/SummarizerUI';
export default function Home() {
return (
<main className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 py-12">
<SummarizerUI />
</main>
);
}
Update src/app/layout.tsx to add metadata:
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: 'AI Summarizer | Powered by Mastra',
description: 'Intelligent content summarization using AI agents',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
Let's test everything works correctly.
npm run dev
Open http://localhost:3000 in your browser.
https://blog.google/technology/ai/google-gemini-ai/
You've successfully connected your Mastra AI agent to a beautiful Next.js frontend. You now have a production-ready application that:
The full working code for both Part 1 and Part 2 is available on GitHub:
Repository: github.com/yourusername/ai-summarizer-mastra
Happy building!
If you found this tutorial helpful, consider sharing it with other developers learning AI agent development!
2025-11-12 17:06:15
The DRY principle (Don’t Repeat Yourself) is probably one of the first rules we learn as developers. Its idea seems flawless: don’t repeat code, don’t duplicate knowledge, and move everything that repeats into a single place.
In practice, this means that if a piece of logic appears in multiple places, it should be centralized - as a function, class, module, or component.
This makes the project cleaner, easier to maintain, and safer to modify. Fix a bug in one place - and it disappears everywhere.
In business logic, especially on the backend, DRY works beautifully. Data validation, discount calculations, and authorization rules are perfect examples of knowledge that must be centralized. When such logic is scattered across different services, inconsistencies and bugs are inevitable. In these cases, DRY can literally save months of work, make the system predictable, and simplify refactoring.
But my personal experience - and I mostly work with startups and fast-changing frontend projects — has shown that this principle, when applied dogmatically, can do real damage. When requirements change every week and design lives its own life, strict DRY often becomes a development bottleneck rather than a path to clean code. The more “mature” a project becomes, the “drier” it can be. But in the early stages, too much dryness can be fatal.
DRY wasn’t created as a fight against repeating code just for aesthetics - it was a fight against repeating knowledge. The authors of the principle, Andrew Hunt and David Thomas, explained in The Pragmatic Programmer that if the same business rule appears in multiple places, it’s a sign of an architectural flaw.
The core idea is simple:
This ensures consistency, reduces the risk of bugs, and simplifies testing. DRY makes code stable and transparent when it comes to recurring data structures, formulas, business rules, calculations, or constants.
Everything related to backend logic is where DRY shines. For example:
In these areas, duplicating logic leads to divergence and unpredictable behavior. Here, DRY doesn’t just save lines of code — it preserves the integrity of the system’s meaning.
The biggest mistake is applying DRY automatically without context.
The desire to “combine everything” often creates monstrous abstractions.
Developers merge similar chunks of code and end up with a giant function full of conditions, flags, and optional parameters that tries to do everything — but no one remembers exactly what.
Such “universal” modules become black boxes: hard to modify, painful to debug, and confusing to newcomers.
Once, I worked on a project where I just needed to change the color of a button on one page. But the button component was so “dried up” — with factories, dozens of props, and global styles — that it took me four hours to make a tiny change. Ironically, there were only about ten buttons in the entire interface. That was a textbook example of how blind DRY can turn simplicity into chaos.
When everything depends on one shared module, every small change becomes a minefield. You fix something for one page — and something breaks elsewhere. This creates tight coupling, where independent parts of the system become hostages of shared logic.
Ironically, the famous DRY argument “fix it once, fix it everywhere” works in reverse: introduce a bug once — and it spreads everywhere. The result is a fragile, rigid system where touching one line can cause a chain reaction.
DRY was never meant to eliminate every visual repetition — only repetition of knowledge. But developers often merge pieces that are only visually similar, not conceptually identical. At first, it feels smart: “Why write this twice?”. Then you realize these pieces evolve differently, and the abstraction becomes a burden.
In startups, I see this all the time. Requirements change weekly, designs daily. Premature abstraction slows everything down — you keep adding exceptions, flags, and branching logic just to preserve a “shared” structure.
It’s often far easier to duplicate a small piece of code, let it evolve independently, and only later refactor it once the functionality stabilizes. This follows the “Rule of Three”: if you write the same thing three times, that’s the moment to abstract.
In microservice architectures, DRY can be downright dangerous.
Developers create shared libraries “to avoid duplication,” but in reality, they end up with a distributed monolith — a system where all services depend on one shared core.
Now you can’t deploy anything independently; every minor update risks breaking multiple components. Over time, those shared “commons” libraries turn into dumping grounds for everything no one dares to delete. Instead of elegance, DRY breeds technical debt.
Frontend teams are especially vulnerable: shared “UI utility” libraries quickly become bloated, outdated, and terrifying to touch. Everything looks dry, but nobody understands how it works anymore.
In the real world, systems are rarely symmetrical or stable.
In frontend projects especially, “universal components” often become nightmares: a single mega-component for buttons, taking ten props and supporting countless variants — until someone clones it anyway to make “a rounded one with a gradient hover.”
When the designer suddenly wants three shapes of buttons, forty color shades, and unique hover states, the DRY dream collapses. Sometimes it’s far better to have three separate, simple components than one monstrous one.
In young, evolving projects, universal abstractions don’t speed things up — they create inertia. When the priority is speed, iteration, and experimentation, duplication is often cheaper and safer.
Sometimes, duplication is an intentional, pragmatic decision.
If similar pieces of code serve different purposes, evolve at different paces, or belong to different contexts — keep them separate. This mindset even has a name: WET — Write Everything Twice.
It’s not an argument for chaos — it’s an acknowledgment that duplication is often cheaper than the wrong abstraction. This is especially true in frontend development and young products, where requirements change faster than architecture can stabilize.
In business logic and backend development, DRY still shines — for calculations, validation, and data access layers. But in user interfaces and early-stage products, pragmatism must win over dogma.
DRY is an excellent principle — when used consciously. It keeps systems consistent and maintainable. But when treated like a religion, it becomes a source of rigidity and pain.
In frontend development and fast-moving projects, dogmatic DRY causes more harm than good. The faster your product evolves, the more important flexibility, independence, and clarity become.
Let the code repeat a little — as long as it stays readable, local, and easy to change. After all, programming isn’t about eliminating repetition — it’s about balancing clarity, speed, and common sense.
2025-11-12 17:06:02
We’re excited to share that our CubenSquare experts have recently been onboarded as consultants for a leading tech company to regulate and automate their entire testing process.
Our team is currently implementing a complete shift — from manual testing to automated frameworks using Selenium, Python, and CI/CD pipelines to ensure faster, smarter, and more reliable deployments. 🚀
And here’s the best part —
You can learn directly from these experts who are handling real-time automation consulting projects right now!
Our Software Testing Training Program is designed to take you from scratch to hands-on expertise:
Manual + Automation Testing (Selenium / PyTest / API Testing)
CI/CD Integration with Jenkins & GitHub
Real-time Project Scenarios & Internship
Job Assistance + Resume & Mock Interview Support
Get free Internship ( 1 month) + AI topics combo this October
2025-11-12 17:05:03
In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.