<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Yousef Romany — Blog</title>
    <link>https://blogs.yousefx00.online</link>
    <atom:link href="https://blogs.yousefx00.online/rss.xml" rel="self" type="application/rss+xml"/>
    <description>Practical articles on Next.js, React, TypeScript, Node.js and AI agent engineering — written by Yousef Romany, a full-stack developer and AI agent engineer from Luxor, Egypt.</description>
    <language>en-us</language>
    <managingEditor>me@yousefx00.online (Yousef Romany)</managingEditor>
    <lastBuildDate>Mon, 24 Aug 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>How I Price Projects Now (and Why I Stopped Selling Hours)</title>
      <link>https://blogs.yousefx00.online/posts/how-i-price-freelance-projects</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/how-i-price-freelance-projects</guid>
      <description>Hourly billing taxes efficiency. My shift to value-based fixed bids — tiered proposals, honest estimating, and the conversation that closes them.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>Freelancing</category>
      <category>Career</category>
      <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Early in my freelancing journey — back when I was still figuring out <a href="/posts/freelancing-from-luxor">how working from Luxor actually operates</a> — I billed hourly because it felt safe. It took me embarrassingly long to notice the trap: the better I got, the less I earned. A refactor I quoted at 15 hours started taking six. Same quality, same outcome, 60% of the money — effectively a punishment for competence.</p>
<p><img src="/images/posts/freelance-pricing-curve.svg" alt="Chart comparing linear hourly billing against accelerating value-based pricing, showing the widening gap" loading="lazy" /></p>
<h3 id="the-hourly-trap-stated-plainly">The hourly trap, stated plainly</h3>
<p>Hourly billing converts your efficiency into the client&#39;s discount. Worse, it invites micro-audits: itemized timesheets, questions about why Tuesday took four hours, silent suspicion around fast turnarounds. And it caps income at hours × rate — a ceiling you can only push by working more or raising rates incrementally, forever, against resistance.</p>
<p>The client isn&#39;t buying hours. Nobody wants hours. They want the checkout flow that stops leaking carts, the admin panel their team stops complaining about, the AI agent that handles tier-one tickets. Price <em>that</em>.</p>
<h3 id="anchoring-on-value-honestly">Anchoring on value, honestly</h3>
<p>I&#39;m not talking about magic multipliers or charging whatever desperation allows. Practical version:</p>
<ol><li>Ask what the problem costs monthly — abandoned orders, support hours burned, opportunities missed.</li><li>Estimate what fixing it is worth over a conservative window (say, 3–6 months).</li><li>Set the fee at a fraction of that, low enough that saying yes is easy arithmetic for the client.</li></ol>
<p>A booking system that recovers even $800/month in missed appointments justifies a $4,000 build in five months of pure recovered revenue. Framing it that way changes the negotiation from &quot;why so much?&quot; to &quot;when can you start?&quot;</p>
<h3 id="tiered-proposals-do-the-selling-for-me">Tiered proposals do the selling for me</h3>
<p>Every proposal now ships with three options:</p>
<ul><li><strong>Essential</strong> — the core problem solved, nothing else. Safe floor price.</li><li><strong>Complete</strong> — essential plus the polish, analytics, handover docs, training. Priced ~1.8× essential. This is what most clients pick.</li><li><strong>Partnership</strong> — complete plus a retainer for iterations and priority support. For the minority who say yes, it&#39;s the best revenue in the business.</li></ul>
<p>Anchoring works: the middle option reads as obvious once it sits between two others. Without tiers, clients anchor on the single number and negotiate downward. With them, they self-select upward.</p>
<h3 id="estimating-without-lying-to-myself">Estimating without lying to myself</h3>
<p>Fixed-price billing lives or dies on estimation discipline:</p>
<ul><li>Break work into deliverables no bigger than two days each; vague line items hide unknowns.</li><li>Add a 25–40% buffer for integration friction, feedback loops, and the client&#39;s cousin who has opinions about fonts.</li><li>Keep a private log of estimate vs. actual for every project. After ten entries, you know whether you&#39;re systematically optimistic (you are) and by how much.</li></ul>
<p>Scope creep gets handled with scope, not suffering: &quot;Happy to include that — here&#39;s how it adjusts the timeline and price.&quot; New requirement, new line item. Discounts get traded for something: a testimonial, a case study, flexible deadlines, faster payment terms.</p>
<h3 id="where-hourly-still-earns-its-place">Where hourly still earns its place</h3>
<p>Two cases: genuinely open-ended discovery, where nobody knows the shape of the work yet (time-boxed sprint, fixed maximum); and legacy archaeology — inheriting a codebase someone else haunted, where estimates are astrology until you&#39;re inside it. Even then, I convert to fixed price as soon as the fog lifts.</p>
<p>And some red flags mean raise the price or walk: unlimited revisions requested upfront, refusal to do any discovery, &quot;budget is tight but scope is flexible,&quot; or a story about how the last three developers failed them.</p>
<p>Clients don&#39;t pay for your hours — those are gone either way. They pay for the version of their business that exists after you&#39;ve left. Bill accordingly.</p>]]></content:encoded>
    </item>
    <item>
      <title>RAG Pipelines That Actually Work: Lessons From Shipping Retrieval Systems</title>
      <link>https://blogs.yousefx00.online/posts/rag-pipelines-that-work</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/rag-pipelines-that-work</guid>
      <description>Chunking strategy, hybrid search, reranking, and evaluation — the unglamorous decisions that separate RAG demos from systems users actually trust.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>AI Agents</category>
      <category>Architecture</category>
      <pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Everyone has built a RAG demo; few have shipped a RAG system. The demo loads a PDF into LangChain, vibes for five minutes in a notebook, and gets applause. The system faces 40,000 messy documents, users who ask questions in three languages, and a boss who notices when answers are wrong. In <a href="/posts/ai-agents-production-architecture">my post on production agent architecture</a> I covered the agent loop — here I want to zoom into the part that actually determines quality: retrieval.</p>
<p>Because here&#39;s the uncomfortable truth: once you&#39;re above a certain model threshold, <strong>retrieval quality is the product</strong>. The LLM can only reason over what you hand it.</p>
<p><img src="/images/posts/rag-pipeline.svg" alt="RAG pipeline diagram showing offline ingestion into a vector store and per-query hybrid retrieval with reranking" loading="lazy" /></p>
<h3 id="ingestion-decisions-haunt-you-forever">Ingestion decisions haunt you forever</h3>
<p>Ingestion is boring, which is exactly why teams rush it and pay later. The decisions that matter:</p>
<ul><li><strong>Chunk size: 300–500 tokens with ~15% overlap</strong> is my default starting point. Smaller chunks retrieve precisely but lose context; larger ones keep context but dilute the embedding until everything matches everything.</li><li><strong>Split on structure, not character count.</strong> A chunk that starts mid-sentence and ends mid-table is garbage no matter its size. Respect headings, paragraphs, and code blocks — recursive splitting on separators beats fixed-window chopping every time.</li><li><strong>Attach metadata religiously:</strong> source document, section title, date, version. Filters on metadata fix half of all &quot;wrong answer&quot; complaints (&quot;ignore anything older than 2025&quot;).</li><li><strong>Deduplicate near-identical docs.</strong> Five versions of the same policy PDF means five competing chunks fighting for top-k.</li></ul>
<h3 id="chunking-is-craft-embeddings-are-commodity">Chunking is craft; embeddings are commodity</h3>
<p>Teams agonize over embedding model benchmarks while shipping chunks that start mid-sentence. Wrong priority. Modern embedding models are all good and their differences shrink yearly; no model rescues a chunk whose boundaries destroy meaning. If you change one thing about ingestion, make it &quot;split where ideas naturally end.&quot;</p>
<h3 id="hybrid-search-always">Hybrid search, always</h3>
<p>Pure vector search has a blind spot exactly where business users live: exact tokens. SKUs, error codes, function names, invoice numbers — embeddings blur these into semantic mush. <code>ERR_2041</code> should match <code>ERR_2041</code>, not a paragraph about &quot;general connection issues.&quot;</p>
<p>The fix is hybrid search: run BM25 (keyword) and vector search in parallel, merge results with Reciprocal Rank Fusion. It&#39;s a one-line scoring formula over two result lists, and it consistently outperforms either approach alone.</p>
<h3 id="rerank-the-cheapest-quality-jump-you-can-buy">Rerank: the cheapest quality jump you can buy</h3>
<p>Here&#39;s my standard move: retrieve k=50 cheaply with hybrid search, then rerank down to the top 5 with a cross-encoder. The bi-encoder embeddings score documents independently; a cross-encoder reads query and document <em>together</em>, so it catches relevance signals vector search structurally cannot see.</p>
<p>Cost: an extra 100–200ms per query. Quality jump: routinely the largest single improvement in the whole stack. If your pipeline does only two clever things, make them hybrid search and reranking.</p>
<h3 id="evaluate-or-fly-blind">Evaluate or fly blind</h3>
<p>The failure mode of RAG projects is tuning by vibes — change the chunker, ask three questions, feel good, deploy. Then quality quietly regresses and nobody knows why.</p>
<p>What works instead:</p>
<ol><li>Build a golden set of 30–50 real questions paired with the source passages that must be retrieved. Mine them from actual support tickets and user queries, not from your imagination.</li><li>Measure recall@k — did the right chunk appear in the top-k at all? This is the metric upstream of answer quality.</li><li>Re-run the set after <em>every</em> change to chunker, embedding model, or k. It takes minutes and catches regressions before users do.</li></ol>
<p>You don&#39;t need an eval platform to start. A script, a JSON file of questions, and honesty are enough.</p>
<h3 id="ground-everything">Ground everything</h3>
<p>Last layer: force citations. Every claim links back to the chunk it came from; if retrieval confidence is low, say &quot;I couldn&#39;t find this in the documentation&quot; instead of improvising. Users forgive a missing answer far more easily than a confident wrong one — and cited answers let them verify in seconds, which is what turns skeptical users into believers.</p>
<p>Users don&#39;t care which embedding model you picked. They care whether the answer cites the right page. Spend your effort accordingly.</p>]]></content:encoded>
    </item>
    <item>
      <title>Server Actions in Practice: Mutations Without the API Layer</title>
      <link>https://blogs.yousefx00.online/posts/server-actions-in-practice</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/server-actions-in-practice</guid>
      <description>Forms that validate, mutate, and revalidate in a single round trip — how I structure Server Actions in production Next.js apps, and where I still write API routes anyway.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>Next.js</category>
      <category>React</category>
      <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>For years, every mutation meant the same ceremony: create an API route, wire up a <code>fetch</code> in a handler, manage loading state by hand, then refetch everything to update the UI. The App Router collapsed that entire ritual into one function call. Server Actions are the feature I was most skeptical about and now miss the most whenever I work outside Next.js.</p>
<p>This builds on the <a href="/posts/nextjs-app-router-patterns">App Router conventions I use everywhere</a> — this post zooms into the mutation half of the picture.</p>
<p><img src="/images/posts/server-actions-pipeline.svg" alt="Server Actions pipeline: from form submit to fresh UI in one round trip" loading="lazy" /></p>
<h3 id="the-whole-pipeline-is-one-function">The whole pipeline is one function</h3>
<p>Here is a real action from a client dashboard, trimmed to its skeleton:</p>
<figure class="code-block"><span class="code-lang">ts</span><pre><code>&#39;use server&#39;;

import { z } from &#39;zod&#39;;
import { revalidatePath } from &#39;next/cache&#39;;
import { db } from &#39;@/lib/db&#39;;
import { getCurrentUser } from &#39;@/lib/auth&#39;;

const UpdateOrder = z.object({
  id: z.string().uuid(),
  status: z.enum([&#39;pending&#39;, &#39;shipped&#39;, &#39;delivered&#39;]),
});

export async function updateOrder(formData: FormData) {
  const user = await getCurrentUser();
  if (!user) return { error: &#39;Unauthorized&#39; };

  const parsed = UpdateOrder.safeParse({
    id: formData.get(&#39;id&#39;),
    status: formData.get(&#39;status&#39;),
  });
  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors };
  }

  await db.order.update({
    where: { id: parsed.data.id, userId: user.id },
    data: { status: parsed.data.status },
  });

  revalidatePath(&#39;/orders&#39;);
  return { ok: true };
}</code></pre></figure>
<p>The form posts directly to it. Validation, authorization, mutation, and cache invalidation happen in one round trip — no <code>api/orders/route.ts</code>, no client-side fetch plumbing, no manual cache busting. The <code>&lt;form&gt;</code> works before hydration even finishes, which means it works on terrible connections too.</p>
<h3 id="rules-i-never-break">Rules I never break</h3>
<ol><li><strong>Treat every action as a public endpoint.</strong> It literally is one — an HTTP POST under the hood. Validate input with a schema, check authorization inside the action, never trust props that arrived from the client.</li><li><strong>Return data, don&#39;t throw, for expected failures.</strong> A validation miss or &quot;duplicate email&quot; is a response, not an exception. Reserve thrown errors for things that should actually blow up.</li><li><strong>Keep actions thin.</strong> The action parses, authorizes, delegates to a function in <code>lib/</code>, and revalidates. Business logic lives where it can be tested without React.</li><li><strong>One concern per action.</strong> <code>updateOrderStatus</code> beats a generic <code>submitOrderForm(mode)</code> every time. Generic actions grow into unreviewable switch statements.</li></ol>
<h3 id="errors-without-trycatch-spaghetti">Errors without try/catch spaghetti</h3>
<p>Pair actions with <code>useActionState</code> and pending states come for free:</p>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>const [state, formAction, pending] = useActionState(updateOrder, null);

return (
  &lt;form action={formAction}&gt;
    &lt;button disabled={pending}&gt;
      {pending ? &#39;Saving…&#39; : &#39;Save&#39;}
    &lt;/button&gt;
    {state?.error &amp;&amp; &lt;p role=&quot;alert&quot;&gt;{String(state.error)}&lt;/p&gt;}
  &lt;/form&gt;
);</code></pre></figure>
<p>That&#39;s the entire loading-state story. No <code>isSubmitting</code> effect, no aborted-fetch cleanup.</p>
<h3 id="optimistic-ui-that-doesnt-lie">Optimistic UI that doesn&#39;t lie</h3>
<p>For small toggles — favorites, checkboxes, archive buttons — waiting on the round trip feels sluggish. <code>useOptimistic</code> paints the expected result immediately and reconciles when the action resolves:</p>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>const [optimisticItems, addOptimistic] = useOptimistic(
  items,
  (state, updated: Item) =&gt; state.map((i) =&gt; (i.id === updated.id ? updated : i))
);

async function toggle(item: Item) {
  startTransition(async () =&gt; {
    addOptimistic({ ...item, done: !item.done });
    await toggleItem(item.id);
  });
}</code></pre></figure>
<p>If the server rejects, the UI snaps back automatically. Users get instant feedback; the database stays the source of truth.</p>
<h3 id="where-i-still-write-api-routes">Where I still write API routes</h3>
<p>Server Actions aren&#39;t a total replacement:</p>
<ul><li><strong>Webhooks</strong> — Stripe and GitHub don&#39;t submit your forms; they need real endpoints with signature verification.</li><li><strong>GET semantics</strong> — anything another service polls benefits from route handlers with proper caching headers.</li><li><strong>Long-running work</strong> — an action that streams video transcoding progress is fighting the model; queue a job instead.</li><li><strong>Non-browser clients</strong> — mobile apps and cron jobs want boring JSON endpoints.</li></ul>
<p>Roughly ninety percent of mutations in my projects are actions. The remaining ten percent keep route handlers employed.</p>
<p>Server Actions didn&#39;t remove the backend — they removed the boilerplate between it and your form. That distinction is why they stick.</p>]]></content:encoded>
    </item>
    <item>
      <title>Next.js App Router Patterns I Use in Every Production Project</title>
      <link>https://blogs.yousefx00.online/posts/nextjs-app-router-patterns</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/nextjs-app-router-patterns</guid>
      <description>Server Components by default, streaming with Suspense, type-safe params, and metadata that ranks — the App Router conventions I reach for in every client build.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>Next.js</category>
      <category>React</category>
      <category>TypeScript</category>
      <pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>After shipping a dozen production projects on the App Router — portfolio sites, dashboards, AI tools — the same set of patterns keeps proving itself. These are the conventions I now apply before writing a single feature, because they prevent the two most common App Router problems: accidental client-side bloat and broken SEO.</p>
<h3 id="server-by-default-client-at-the-leaves">Server by default, client at the leaves</h3>
<p>The biggest mindset shift with the App Router is that <strong>every component is a Server Component until you prove otherwise</strong>. Instead of sprinkling <code>&#39;use client&#39;</code> everywhere and pulling it out later, I invert the workflow: keep pages fully server-rendered, then push interactivity down into small leaf components.</p>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>// app/page.tsx — server component (default)
import { ProductFilters } from &#39;@/components/product-filters&#39;;
import { getProducts } from &#39;@/lib/data&#39;;

export default async function Page() {
  const products = await getProducts();
  return (
    &lt;main&gt;
      &lt;ProductFilters products={products} /&gt;
    &lt;/main&gt;
  );
}</code></pre></figure>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>// components/product-filters.tsx — the only interactive part
&#39;use client&#39;;

export function ProductFilters({ products }: { products: Product[] }) {
  const [query, setQuery] = useState(&#39;&#39;);
  // ...
}</code></pre></figure>
<p>The rule of thumb: <strong>if a component doesn&#39;t need state, effects, or browser APIs, it doesn&#39;t need <code>&#39;use client&#39;</code></strong>. This one discipline routinely cuts client JavaScript bundles by half or more.</p>
<h3 id="treat-params-and-searchparams-as-promises">Treat <code>params</code> and <code>searchParams</code> as promises</h3>
<p>Since Next.js 15, dynamic APIs are asynchronous. Old tutorials show synchronous access, which breaks silently or throws. Every dynamic route I write follows this shape:</p>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>type Props = {
  params: Promise&lt;{ slug: string }&gt;;
};

export async function generateMetadata({ params }: Props): Promise&lt;Metadata&gt; {
  const { slug } = await params;
  const post = await getPost(slug);
  return {
    title: post.title,
    description: post.description,
    alternates: { canonical: `/posts/${slug}` },
  };
}

export async function Page({ params }: Props) {
  const { slug } = await params;
  const post = await getPost(slug);
  return &lt;Article post={post} /&gt;;
}</code></pre></figure>
<p>Pair this with React&#39;s <code>cache()</code> so <code>getPost</code> runs once per request even when both the page and its metadata call it:</p>
<figure class="code-block"><span class="code-lang">ts</span><pre><code>import { cache } from &#39;react&#39;;

export const getPost = cache(async (slug: string) =&gt; {
  return db.posts.findFirst({ where: { slug } });
});</code></pre></figure>
<h3 id="stream-slow-sections-with-suspense">Stream slow sections with Suspense</h3>
<p>Nothing hurts perceived performance like waiting for your slowest query before painting anything. Instead of blocking the whole route, wrap slow sections in Suspense and let the shell render instantly:</p>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>export default function DashboardPage() {
  return (
    &lt;&gt;
      &lt;Header /&gt;
      &lt;Suspense fallback={&lt;StatsSkeleton /&gt;}&gt;
        &lt;SlowRevenueStats /&gt;
      &lt;/Suspense&gt;
      &lt;FastNavLinks /&gt;
    &lt;/&gt;
  );
}</code></pre></figure>
<p>Users see the header and navigation immediately; only the genuinely slow card streams in when its data resolves. It&#39;s the single highest-impact performance change you can make in under ten minutes.</p>
<h3 id="metadata-is-a-feature-not-an-afterthought">Metadata is a feature, not an afterthought</h3>
<p>Every project gets the same baseline:</p>
<ul><li>A <code>metadataBase</code> in the root layout so OG images resolve to absolute URLs.</li><li>Per-page <code>generateMetadata</code> with unique titles, descriptions, and canonicals.</li><li>Static routes (<code>sitemap.ts</code>, <code>robots.ts</code>) generated from real data.</li><li>JSON-LD structured data for the entities the site actually represents.</li></ul>
<figure class="code-block"><span class="code-lang">tsx</span><pre><code>export const metadata: Metadata = {
  metadataBase: new URL(&#39;https://example.com&#39;),
};</code></pre></figure>
<p>Skipping canonical URLs is the mistake I see most often — especially on sites reachable from multiple domains, where duplicate-content issues quietly tank rankings.</p>
<h3 id="colocate-everything-share-almost-nothing">Colocate everything, share almost nothing</h3>
<p>Route groups let me give marketing pages one layout and app pages another without touching the URL structure. Shared UI lives in a <code>components/</code> folder; data access lives beside the database schema, not inside components. When a component imports data-fetching logic directly, refactoring the backend becomes a frontend change too — and that coupling compounds fast.</p>
<h3 id="the-checklist-i-run-before-every-deploy">The checklist I run before every deploy</h3>
<ol><li>No <code>&#39;use client&#39;</code> above the fold unless it earns its bytes.</li><li>All dynamic APIs awaited; types reflect the promise wrappers.</li><li>Slow data behind <code>&lt;Suspense&gt;</code> boundaries.</li><li>Unique title + description + canonical per route.</li><li><code>sitemap.xml</code> and <code>robots.txt</code> generated from source-of-truth data.</li><li>Lighthouse run on a production build, not dev mode.</li></ol>
<p>None of these are clever. That&#39;s exactly why they work — boring conventions, applied every time, are what keep App Router projects maintainable a year later.</p>]]></content:encoded>
    </item>
    <item>
      <title>Building AI Agents That Actually Ship: A Practical Architecture</title>
      <link>https://blogs.yousefx00.online/posts/ai-agents-production-architecture</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/ai-agents-production-architecture</guid>
      <description>Most agent demos die outside the playground. Here&apos;s the production architecture I use — tool schemas, guardrails, memory, and evaluation — that survives real users.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>AI Agents</category>
      <category>Node.js</category>
      <category>Architecture</category>
      <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>The gap between an agent demo and an agent in production is enormous. Demos are linear, forgiving, and watched. Production agents face hostile inputs, flaky APIs, and users who will type anything. After building automation agents for several businesses, I&#39;ve settled on an architecture that holds up outside the playground.</p>
<h3 id="the-core-loop-is-boring-on-purpose">The core loop is boring on purpose</h3>
<p>Every agent I ship has the same skeleton: a loop that calls a model, executes tools, feeds results back, and knows when to stop.</p>
<figure class="code-block"><span class="code-lang">ts</span><pre><code>async function runAgent(task: string, maxSteps = 10) {
  const messages = [{ role: &#39;user&#39;, content: task }];

  for (let step = 0; step &lt; maxSteps; step++) {
    const response = await llm.chat({
      messages,
      tools: toolSchemas,
    });

    if (!response.toolCalls?.length) return response.text;

    for (const call of response.toolCalls) {
      const result = await executeTool(call);
      messages.push(toolResult(call.id, result));
    }
  }

  throw new AgentBudgetExceededError(maxSteps);
}</code></pre></figure>
<p>Three details matter more than they look:</p>
<ol><li><strong><code>maxSteps</code> is a hard budget.</strong> Runaway loops burn money and trust.</li><li><strong>Tool results get truncated before entering context.</strong> A 50KB API response will poison every subsequent call.</li><li><strong>Errors become observations, not exceptions.</strong> The model can often recover if you tell it a tool failed and why.</li></ol>
<h3 id="tools-are-apis-so-design-them-like-apis">Tools are APIs, so design them like APIs</h3>
<p>The model reads your tool schema the way a developer reads docs. Vague names and mushy descriptions produce vague calls. My rules:</p>
<ul><li>Name tools after verbs: <code>search_orders</code>, <code>send_email</code>, <code>create_invoice</code>.</li><li>Describe <em>when</em> to use the tool, not just what it does.</li><li>Keep parameters flat and required-optional clean — models handle flat schemas far better than deep nesting.</li></ul>
<figure class="code-block"><span class="code-lang">ts</span><pre><code>const searchOrders = tool({
  name: &#39;search_orders&#39;,
  description:
    &#39;Find customer orders by email, order ID, or date range. Use when the user asks about order status.&#39;,
  parameters: z.object({
    email: z.string().email().optional(),
    orderId: z.string().optional(),
    limit: z.number().max(20).default(5),
  }),
});</code></pre></figure>
<h3 id="guardrails-before-intelligence">Guardrails before intelligence</h3>
<p>An agent with write access is a liability until proven otherwise. Before any cleverness:</p>
<ul><li><strong>Allowlist actions.</strong> The agent may call <code>draft_refund</code>, never <code>issue_refund</code>. A human approves drafts.</li><li><strong>Validate every tool argument</strong> at the boundary with a schema library — treat model output like untrusted user input, because it is.</li><li><strong>Log every step</strong> with inputs, outputs, token counts, and latency. When something goes wrong at 2 AM, this log is the whole story.</li></ul>
<h3 id="memory-short-context-beats-clever-embeddings">Memory: short context beats clever embeddings</h3>
<p>For most business agents, &quot;memory&quot; means three things:</p>
<div class="table-wrap"><table><thead><tr><th>Layer</th><th>What it holds</th><th>Lifetime</th></tr></thead><tbody><tr><td>Conversation</td><td>Current message thread</td><td>Session</td></tr><tr><td>Working notes</td><td>Facts extracted mid-task</td><td>Task</td></tr><tr><td>Long-term</td><td>User preferences, past outcomes</td><td>Forever (small)</td></tr></tbody></table></div>
<p>I store working notes as plain key-value facts appended to the system prompt rather than reaching straight for a vector database. Retrieval adds failure modes; a 2KB summary of relevant facts usually outperforms top-k chunk lookup for narrow domains.</p>
<h3 id="evaluate-continuously-not-once">Evaluate continuously, not once</h3>
<p>The difference between teams that trust their agents and teams that don&#39;t is evaluation. Before any change ships, a fixed suite of tasks must pass:</p>
<figure class="code-block"><span class="code-lang">text</span><pre><code>Task 12: &quot;Where is my order?&quot; + no order id in history
  expect: asks clarifying question, never calls search_orders
Task 13: refund request under $50
  expect: draft_refund created, approval flag set</code></pre></figure>
<p>It&#39;s crude. It&#39;s also the only reason I can change a prompt on Friday afternoon without spending the weekend reading error logs.</p>
<h3 id="what-id-tell-my-past-self">What I&#39;d tell my past self</h3>
<p>Ship the smallest agent that touches one workflow end-to-end, instrument everything, and expand only when the evals say you can. The impressive part isn&#39;t the model — anyone can rent those. The impressive part is the scaffolding that makes it dependable, and dependable is what customers actually pay for.</p>]]></content:encoded>
    </item>
    <item>
      <title>How I Built This Site: A Dark, High-Performance Portfolio with Next.js</title>
      <link>https://blogs.yousefx00.online/posts/how-i-built-this-site</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/how-i-built-this-site</guid>
      <description>The design system, CSS architecture, and performance decisions behind yousefx00.online — one dark theme, three fonts, zero UI libraries.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>Next.js</category>
      <category>CSS</category>
      <category>Performance</category>
      <pubDate>Sat, 02 May 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>My portfolio at <a href="https://yousefx00.online" target="_blank" rel="noopener noreferrer">yousefx00.online</a> started with a constraint I set for myself: no UI frameworks, no component library — just Next.js, CSS Modules, and a strict design system. This post is the breakdown of how it&#39;s built and why.</p>
<h3 id="one-accent-color-used-like-punctuation">One accent color, used like punctuation</h3>
<p>The entire visual identity is a near-black canvas (<code>#05090C</code>) with a single lime accent (<code>#A6DA04</code>). Everything else is grayscale. That restraint is what makes it feel intentional rather than decorated.</p>
<p>The tokens live in <code>:root</code> as CSS custom properties, so every component reads from the same palette:</p>
<figure class="code-block"><span class="code-lang">css</span><pre><code>:root {
  --bg-primary: #05090C;
  --bg-surface: #0D1216;
  --accent: #A6DA04;
  --text-secondary: #A7ABAD;
  --border-subtle: #1B2226;
  --font-heading: &#39;Sora&#39;, sans-serif;
  --font-body: &#39;Inter&#39;, sans-serif;
  --font-mono: &#39;JetBrains Mono&#39;, monospace;
}</code></pre></figure>
<p>If I ever redesign, I change twelve lines — not two hundred components.</p>
<h3 id="three-fonts-each-with-one-job">Three fonts, each with one job</h3>
<ul><li><strong>Sora</strong> for headings — geometric, techy, holds up at display sizes.</li><li><strong>Inter</strong> for body text — the safest readable choice on screens.</li><li><strong>JetBrains Mono</strong> reserved for labels, code, and the little <code>// eyebrow</code> details that give the page its engineering flavor.</li></ul>
<p>All three load in a single Google Fonts request, which keeps DNS lookups and connection overhead to one round trip.</p>
<h3 id="the-mono-label-trick">The mono-label trick</h3>
<p>My favorite detail is the eyebrow label above section titles:</p>
<figure class="code-block"><span class="code-lang">css</span><pre><code>.eyebrow::before {
  content: &#39;&#39;;
  width: 20px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
}</code></pre></figure>
<p>A tiny accent dash plus uppercase mono text reads like a terminal prompt. It costs nothing, scales to any section, and instantly signals &quot;developer made this.&quot;</p>
<h3 id="motion-that-respects-the-user">Motion that respects the user</h3>
<p>Animations are limited to entrance fades, a pulsing status dot, and hover lifts — all under 700ms, all driven by a shared easing curve:</p>
<figure class="code-block"><span class="code-lang">css</span><pre><code>--transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);</code></pre></figure>
<p>No parallax, no scroll-jacking. Motion should confirm an interaction happened, not perform for its own sake.</p>
<h3 id="performance-choices">Performance choices</h3>
<ul><li><strong>Static generation everywhere.</strong> Every page prerenders; there&#39;s no client data fetching at all.</li><li><strong>CSS Modules over utility classes.</strong> Zero runtime cost, scoped by default, and the bundle only ships styles actually imported.</li><li><strong><code>next/image</code> with explicit sizes</strong> for the few raster assets, so layout never shifts.</li><li><strong>One fixed navbar</strong>, blurred only after scroll, so the initial paint stays cheap.</li></ul>
<p>The result is a site that scores green across Core Web Vitals without a single optimization library.</p>
<h3 id="what-id-do-differently">What I&#39;d do differently</h3>
<p>Three things, honestly: ship a real blog from day one (you&#39;re reading it), add Open Graph images earlier than feels necessary, and write the <code>sitemap.ts</code> before launch instead of after submitting to Search Console and wondering why coverage reports look sad.</p>
<p>Design systems aren&#39;t about limiting creativity — they&#39;re about spending your creativity where users can feel it.</p>]]></content:encoded>
    </item>
    <item>
      <title>Freelancing From Luxor, Egypt: What Working With Global Clients Taught Me</title>
      <link>https://blogs.yousefx00.online/posts/freelancing-from-luxor</link>
      <guid isPermaLink="true">https://blogs.yousefx00.online/posts/freelancing-from-luxor</guid>
      <description>Remote full-stack development from Upper Egypt — timezone strategies, communication habits, and why &apos;available worldwide&apos; is a real business advantage.</description>
      <author>me@yousefx00.online (Yousef Romany)</author>
      <category>Career</category>
      <category>Freelancing</category>
      <pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I build web applications and AI agents from Luxor — yes, the one with the temples — for clients several time zones away. People are often curious about how that works. Short answer: surprisingly well. Longer answer below.</p>
<h3 id="the-timezone-myth">The timezone myth</h3>
<p>The first objection everyone raises: &quot;But you&#39;re hours ahead of your clients.&quot; In practice, offset is only a problem if your work requires synchronized hours. Most development doesn&#39;t. What actually matters:</p>
<ul><li><strong>Overlap windows beat matching schedules.</strong> I keep two to three hours aligned with the client&#39;s afternoon. Everything else is async.</li><li><strong>Async is a feature.</strong> When a client in New York sends briefs at the end of their day, they wake up to finished work. Handoffs across timezones turn waiting time into building time.</li><li><strong>Write things down.</strong> Async collaboration forces clear specs, recorded decisions, and written status updates — which eliminates half the miscommunication of meeting-heavy projects anyway.</li></ul>
<h3 id="communication-is-the-actual-job">Communication is the actual job</h3>
<p>Nobody hires a developer in Egypt for the price alone; the race to the bottom on rates is unwinnable and miserable. What wins contracts is being the person who replies within hours, ships weekly, and flags risks before they become surprises.</p>
<p>My habits that clients consistently mention:</p>
<ol><li>A short Friday update — what shipped, what&#39;s next, what&#39;s blocked.</li><li>Demos over descriptions. A Loom of the feature working ends debates instantly.</li><li>Saying &quot;here&#39;s what I&#39;d do instead&quot; when a request would hurt the product. Clients pay for judgment, not keystrokes.</li></ol>
<h3 id="local-reality-check">Local reality check</h3>
<p>Working remotely from Upper Egypt comes with friction people don&#39;t see: payment rails need creative routing, some tools gate features by region, and power or internet hiccups demand backups (mine: a mobile hotspot and a UPS). None of it is glamorous, all of it is solvable, and none of it affects deliverable quality.</p>
<p>What the location <em>does</em> give me: deep focus. Luxor is quiet, cost of living lets me be selective with projects, and the distance from tech-scene noise makes it easier to judge work by craft rather than hype.</p>
<h3 id="advice-for-developers-outside-the-big-hubs">Advice for developers outside the big hubs</h3>
<ul><li>Pick a specialty and publish proof you can do it. This blog exists partly for that reason.</li><li>Charge by value where possible; hourly billing punishes efficiency.</li><li>Treat every client interaction as portfolio material — testimonials compound faster than skills.</li><li>Build in public. A GitHub graph and honest write-ups beat any resume line.</li></ul>
<p>The internet made geography irrelevant for code. It never made reliability optional — and that&#39;s exactly why developers anywhere in the world can compete for anywhere-else work.</p>]]></content:encoded>
    </item>
  </channel>
</rss>