<?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>Fanis Mahmalat</title>
    <link>https://fanismahmalat.com/blog/</link>
    <description>Writing on software development by Fanis Mahmalat.</description>
    <language>en</language>
    <atom:link href="https://fanismahmalat.com/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Coding Agents: How I Run Several at Once with Herdr</title>
      <link>https://fanismahmalat.com/blog/herding-coding-agents-with-herdr/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/herding-coding-agents-with-herdr/</guid>
      <pubDate>Sat, 07 Nov 2026 18:58:20 GMT</pubDate>
      <description>How I use Herdr, a terminal multiplexer built for coding agents, to keep several sessions running, see which one needs me, and let agents pass work to each other.</description>
      <content:encoded><![CDATA[<p>I work with microservices, so my setup with coding agents grew one repo at a time. Each service got its own IDE window, and each window got its own Claude Code session. On a busy day I spent more time hopping between those windows than doing anything useful, checking on one session, then the next, then the next. The worst part was that I never knew which one actually needed me. A session could be waiting on my answer while I was busy staring at a different one.</p><p>If you run more than one coding agent at a time, you probably know the feeling. The Herdr site sums up the problem well. Agents often die when you close the window. You can't tell which agent needs your attention. And handing results from one agent to another means copying and pasting by hand.</p><p>I installed Herdr three days ago to fix exactly this. This post covers what it is and the ideas that make it work.</p><h2>What Herdr is</h2><p>The project describes itself as &quot;the runtime your coding agents live on&quot;. Put more plainly, it's a terminal multiplexer built for coding agents. If you've used tmux, the basic shape will feel familiar. A background server keeps your terminals running, and a client attaches to show them. The difference is that Herdr is mouse-first and agent-aware.</p><p>It ships as a single Rust binary, with no Electron. It's free and open source under the Apache 2.0 license. Everything here is based on version 0.9.5 of the terminal app. It's early days, but I've been fascinated by how powerful it is, and I plan to keep testing it. The Herdr site now also promotes a desktop app. This post is about the original terminal version.</p><h2>Idea one: a persistent server</h2><p>The first thing Herdr fixes is the most basic one. Your panes live in a background server, not in the window you happen to be looking at. If you detach, close the terminal, or lose your SSH connection, the agents keep working. Run <code>herdr</code> again and you're right back where you left off.</p><p>Since version 0.9.4, closing the shell that started Herdr doesn't stop the server either, and sessions now <strong>survive a Mac restart</strong>. That's exactly the kind of safety net I want when several agents are mid-task.</p><h2>Idea two: workspaces, tabs and panes</h2><p>Herdr organizes everything into three layers.</p><ul><li><p>A <strong>workspace</strong> is a project-level container, one per repo or per task. With microservices, that maps neatly to one workspace per service.</p></li><li><p>A <strong>tab</strong> is a layout inside a workspace. For example, I might have one tab for agents, one for logs and one for the dev server.</p></li><li><p>A <strong>pane</strong> is a real terminal, split to the right or downward, and it survives the client detaching.</p></li></ul><p>Every one of these gets a short, stable ID, such as <code>w1</code> for a workspace, <code>w1:t1</code> for a tab, and <code>w1:p1</code> for a pane. IDs are never reused after something is closed, which matters a lot once scripts and agents start pointing at them.</p><p>You can drive all of this with the mouse: click to focus, drag split borders, right-click for menus. Keybindings are optional. If you prefer tmux habits, there's a prefix key, Control-B by default, followed by a single action. For example, prefix then <code>v</code> splits right, and prefix then <code>c</code> opens a new tab.</p><p>There's one more feature that caught my eye, and that's <strong>worktrees</strong>. The <code>herdr worktree create</code> command makes a git worktree, opens it as its own workspace, and groups it under the parent repo in the sidebar. That gives each agent its own branch and its own folder, so they can work in parallel without trampling each other's files.</p><h2>Idea three: agents with states</h2><p>This is the part that makes Herdr more than a nice tmux alternative, and the one that fixed my window-hopping problem. Herdr recognizes the coding agent running in each pane and gives it a state. It does this by finding the agent's process and reading the bottom of the live screen, then matching it against detection rules that update automatically.</p><p>The states are simple:</p><ul><li><p><strong>Idle</strong> means the agent is at its prompt, ready for input.</p></li><li><p><strong>Working</strong> means it's busy.</p></li><li><p><strong>Blocked</strong> means it's showing an approval, question or permission prompt. This is the one I care about most. Herdr only knows the prompts it's been taught, so an unfamiliar question can still show as idle.</p></li><li><p><strong>Done</strong> means it finished, but I haven't looked yet. A done agent stays visible in the sidebar until I view it.</p></li><li><p><strong>Unknown</strong> means an agent is there, but Herdr can't tell what it's doing.</p></li></ul><p>These states roll up per workspace in the sidebar, so one glance tells me which project has an agent waiting on me. Herdr can also send a notification when a background agent finishes or needs input, either inside Herdr, in the terminal, or as a system notification.</p><p>Detection covers a long list of agents, including Claude Code, Codex, GitHub Copilot CLI, Cursor Agent, OpenCode, Gemini CLI and more. For many of them there's also an optional integration, which reports the session identity so Herdr can resume the same conversation after a server restart.</p><h2>The real trick: agents driving agents</h2><p>Herdr has a command-line interface and a socket API, and they aren't just for me. Any script, or any agent, can use them too. That opens the door to orchestration, where one agent coordinates the others.</p><p>Picture asking one agent to handle a bigger job. It splits the work into roles, such as one agent to research, one to build and one to review. It starts each of them, gives each a name, and sends each one its instructions. Then it waits for each one to finish, reads what it wrote, and decides what happens next. Maybe it passes the result along to the next agent, or sends it back with fixes. There's no copy-paste in that loop, and you don't have to sit in the middle of it.</p><p>Even more interesting, the agent doesn't just use Herdr. It can rearrange it to fit the task. It can lay out its own workspace and split new panes right beside itself without stealing your focus. It can start exactly the agents it needs in those panes. It can even give each one its own worktree, the feature I mentioned earlier, so they never trip over each other's files. Herdr stops being a workspace only you arrange and becomes one the agent arranges too.</p><p>A couple of safeguards keep this from turning chaotic. A prompt won't be typed into an agent that's blocked on a question, so an instruction can't accidentally answer a permission prompt. And a timeout isn't proof that nothing was sent, so the rule is to read the agent before retrying.</p><p>To get Claude Code playing along, run <code>herdr --skill</code> and save what it prints as a Claude Code skill. That skill teaches Claude Code to use all of this politely, like never closing a pane it didn't create.</p><h2>Getting started</h2><p>On macOS or Linux, you can use the install script from <a href="https://github.com/herdrdev/herdr#install" target="_blank" rel="noopener noreferrer">herdr</a> github repo, or Homebrew with <code>brew install herdr</code>.</p><p>There are also options for mise, Nix and Windows. Once it's installed:</p><ol><li><p>Move into a project folder and run <code>herdr</code>.</p></li><li><p>Start <code>claude</code>, <code>codex</code> or another agent in a pane. Herdr detects it automatically.</p></li><li><p>Optionally run <code>herdr integration install claude</code> so sessions can resume after a restart.</p></li></ol><p>To stay current, run <code>herdr update </code>. There are also eleven built-in themes if you want Herdr to look the part.</p><h2>Wrapping up</h2><p>Before Herdr, running several agents meant babysitting IDE windows. Now my agents keep running when I close things, the sidebar tells me who needs me, and the agents can pass work to each other without me in the middle.</p><p>If you're already juggling two or three coding agents, give it an afternoon. Start with a single workspace and a couple of panes, then try letting one agent prompt another. That's the moment it clicked for me.</p>]]></content:encoded>
    </item>
    <item>
      <title>pnpm 11 turns on supply-chain defenses by default — and it'll break your CI if you're not ready</title>
      <link>https://fanismahmalat.com/blog/pnpm-11-migration-checklist/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/pnpm-11-migration-checklist/</guid>
      <pubDate>Mon, 02 Nov 2026 07:03:31 GMT</pubDate>
      <description>pnpm 11 makes three security defaults stricter and replaces five build settings with allowBuilds. Here's what breaks in CI and how a small team can roll it out.</description>
      <content:encoded><![CDATA[<p>pnpm 11 isn't just a version bump — three defaults flip from permissive to restrictive, each one aimed at the same place supply-chain attacks actually land: packages nobody reviewed, resolved from sources nobody vetted, running scripts nobody approved. If you're running pnpm on a small team — no dedicated platform engineer, no time to read a 40-page migration guide line by line — upgrading on a Friday afternoon and hoping for the best is exactly how this release turns into an afternoon of debugging why <code>pnpm install</code> suddenly fails in CI. Here's what actually changed, where the sharp edges are, and a rollout plan that doesn't require blocking the whole team.</p><h2>What actually changed</h2><p>Three settings ship with stricter defaults in v11:</p><ul><li><p><code>minimumReleaseAge</code> (default <code>1440</code>, in minutes — 24 hours): pnpm won't install a dependency version, direct or transitive, until it's been on the registry for at least this long. That's the cooldown — a malicious version published an hour ago can't get pulled into anyone's install before someone notices and yanks it.</p></li><li><p><code>blockExoticSubdeps</code> (default <code>true</code>): only the dependencies listed directly in your root <code>package.json</code> may resolve from a git repo or a tarball URL. Every transitive dependency has to come from a trusted source — your registry, a local path, a workspace link, or a trusted GitHub repo. A dependency three levels deep quietly pointing at someone's GitHub fork no longer flies.</p></li><li><p><code>strictDepBuilds</code> (default <code>true</code>): if any dependency has an unreviewed build or postinstall script, <code>pnpm install</code> now exits with a non-zero code instead of just running it.</p></li></ul><p>Put together: pnpm used to trust the registry and your dependency tree by default. v11 flips that — everything is unreviewed until you say otherwise.</p><h2>The migration trap: five settings become one</h2><p>The build-approval change is where most teams will actually get stuck, because it isn't additive — it's a replacement. v11 removes <code>onlyBuiltDependencies</code>, <code>onlyBuiltDependenciesFile</code>, <code>neverBuiltDependencies</code>, <code>ignoredBuiltDependencies</code>, and <code>ignoreDepScripts</code> outright and replaces all five with one setting, <code>allowBuilds</code> — a map of package-name patterns to <code>true</code>/<code>false</code> in <code>pnpm-workspace.yaml</code>:</p><p><code>allowBuilds:   esbuild: true   core-js: false</code></p><p>If any of those five old settings were in your config, they're silently ignored now — not an error, just gone — and any package with a postinstall script that isn't explicitly listed in <code>allowBuilds</code> counts as unreviewed. With <code>strictDepBuilds: true</code> as the default, your next clean install just exits non-zero. For a team running <code>esbuild</code>, <code>sharp</code>, native bindings, Playwright, or anything else with a build step, that's most installs.</p><p>The fix is one command, run once per repo: <code>pnpm approve-builds</code>. With no arguments it's interactive — it lists every pending package and lets you approve or deny each one. You can also skip the prompt: <code>pnpm approve-builds esbuild fsevents !core-js</code> approves the first two and explicitly denies the third (a <code>!</code> prefix denies), or <code>pnpm approve-builds --all</code> approves everything pending in one shot if you trust your current tree. Either way, the decision gets written straight into <code>allowBuilds</code> in <code>pnpm-workspace.yaml</code> — commit that file so the rest of the team, and CI, inherit the same approvals instead of everyone hitting the same wall individually.</p><h2>Where your .npmrc settings actually went</h2><p><code>.npmrc</code> isn't gone, but its job shrank: as of v11 it holds registry and auth settings only. Everything else moves to <code>pnpm-workspace.yaml</code>, or to the new global <code>~/.config/pnpm/config.yaml</code> for machine-level settings. Concretely:</p><ul><li><p>Per-scope registries (<code>@scope:registry=...</code> lines) become a <code>registries</code> map in <code>pnpm-workspace.yaml</code>.</p></li><li><p>Per-project <code>.npmrc</code> overrides become <code>packageConfigs</code> in <code>pnpm-workspace.yaml</code>.</p></li><li><p>The <code>node-mirror:&lt;channel&gt;</code> setting becomes <code>nodeDownloadMirrors</code> in <code>pnpm-workspace.yaml</code>.</p></li><li><p>Network settings — <code>httpProxy</code>, <code>httpsProxy</code>, <code>noProxy</code>, <code>localAddress</code>, <code>strictSsl</code>, <code>gitShallowHosts</code> — move to the YAML config too, though pnpm still reads them from <code>.npmrc</code> for now to ease the transition.</p></li><li><p>Environment variables switch from <code>npm_config_*</code> to <code>pnpm_config_*</code>; the old prefix is no longer read at all.</p></li></ul><p>None of these fail loudly if you miss one — they just stop applying, which is worse than a hard error if you're relying on one for a private registry or an internal mirror. Worth a dedicated pass through your current <code>.npmrc</code> rather than assuming the upgrade carries it forward.</p><h2>CI considerations: what stalls if you track latest</h2><p><code>minimumReleaseAge</code> is the one most likely to bite you first, and only in CI, which makes it extra fun to debug. If your pipeline resolves against a floating version — tracking <code>latest</code>, or a loose <code>^</code>/<code>~</code> range — and the version that would satisfy it was published inside the last 24 hours, pnpm won't resolve to it; that's the setting working as designed. Locally this rarely shows up, because you installed a day ago and your lockfile is already pinned. In CI, especially on a fresh runner with no cache, you can hit a brand-new release at exactly the wrong moment.</p><p>Two ways to handle it, and the right one depends on why you're floating in the first place: if you're intentionally tracking fresh releases — an internal package your own team ships multiple times a day, say — set <code>minimumReleaseAge: 0</code> for that package, or workspace-wide if the cooldown doesn't suit your setup. If you're floating by accident because nobody got around to pinning, pin the version instead and let the cooldown do its job everywhere else. Either way, don't discover this failure mode in production CI first — run an install against a clean cache before you flip the upgrade on everyone.</p><h2>A realistic rollout plan for a three-person team</h2><p>Nobody on a three-person team has time to be “the pnpm 11 person” full time, so this is built to take an afternoon, not a sprint.</p><ol><li><p><strong>Pilot on one repo.</strong> Pick the smallest, least business-critical one you've got. Upgrade pnpm, run <code>pnpm approve-builds</code> interactively so you actually see every package being approved, and commit the resulting <code>pnpm-workspace.yaml</code>.</p></li><li><p><strong>Audit .npmrc before touching the others.</strong> Now that you know what moved, go through the pilot repo's old <code>.npmrc</code> line by line and confirm every setting has a new home, or confirm it genuinely was registry/auth and belongs where it is.</p></li><li><p><strong>Run a cold-cache CI install.</strong> Trigger a pipeline run without a warm cache before merging, so you catch a <code>minimumReleaseAge</code> resolution failure now, not three weeks from now at an inconvenient hour.</p></li><li><p><strong>Roll out repo by repo, not all at once.</strong> Copy the now-proven <code>pnpm-workspace.yaml</code> pattern to the next repo rather than re-discovering the same five-settings issue there.</p></li><li><p><strong>Decide your minimumReleaseAge policy once, in writing, not per-repo.</strong> Zero-cooldown for internal fast-moving packages, the 24-hour default for everything external — and apply that consistently instead of re-deciding it under pressure every time CI fails.</p></li></ol><p>The new defaults exist for a real reason, but a restrictive default you don't understand yet is just friction with better branding. Thirty minutes with the pilot repo turns pnpm 11 from a surprise into a checklist.</p><hr /><p><strong>Sources:</strong></p><ul><li><p><a href="https://pnpm.io/blog/releases/11.0" target="_blank" rel="noopener noreferrer">pnpm 11.0 release notes</a></p></li><li><p><a href="https://pnpm.io/settings/dependency-resolution" target="_blank" rel="noopener noreferrer">pnpm settings: dependency resolution</a></p></li><li><p><a href="https://pnpm.io/settings/build" target="_blank" rel="noopener noreferrer">pnpm settings: build</a></p></li><li><p><a href="https://pnpm.io/cli/approve-builds" target="_blank" rel="noopener noreferrer">pnpm CLI: approve-builds</a></p></li></ul>]]></content:encoded>
    </item>
    <item>
      <title>The Six-Minute Loading Screen That Was Secretly an O(n²) Bug</title>
      <link>https://fanismahmalat.com/blog/gta-online-json-parser-performance-bug/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/gta-online-json-parser-performance-bug/</guid>
      <pubDate>Thu, 15 Oct 2026 21:03:22 GMT</pubDate>
      <description>How a modder traced GTA Online's six-minute load times to two parsing bugs, including an O(n²) duplicate check, and cut them by about 70%. With a live demo.</description>
      <content:encoded><![CDATA[<p>For years, GTA Online was infamous for brutal loading times — often five to six minutes just to get into a session. In 2021, a modder going by t0st got curious enough to actually profile it, and found the cause wasn't networking or disk I/O. It was a single-threaded JSON parser doing something almost comically inefficient.</p><p>Before you can join a session, GTA Online loads its net shop catalog: about 10MB of JSON describing roughly 63,000 purchasable items. t0st found two separate problems in how the game read that file. First, its parser was built on sscanf, and most sscanf implementations call strlen on their input — so reading each value meant measuring a 10MB string all over again. Second, after parsing each item, the game scanned every item stored so far to detect duplicates before inserting it. That check scaled quadratically: double the catalog, and the work roughly quadruples, reaching about 1.98 billion comparisons at 63,000 items. It was also pointless, because every item in the catalog was unique. Years of new items had grown that file, so code that was probably fine at launch had become the dominant cost of joining a session.</p><p>The fix itself wasn't exotic. t0st's proof of concept, built entirely client-side with no access to Rockstar's source, cached the string's length so strlen stopped rescanning it, and inserted items directly instead of running the duplicate check. On t0st's machine, loading fell from about six minutes to 1 minute 50 seconds, a cut of roughly 70%. The parser was the bigger culprit: patching it alone brought the load down to 2 minutes 50 seconds, while patching only the duplicate check reached 4 minutes 30 seconds. Rockstar eventually shipped an official update that resolved the problem.</p><site-demo component="gta-load-time" label="GTA Online load time"><p class="demo-fallback">At 63,000 items, checking each new item against all the earlier ones adds up to about 1.98 billion comparisons. On t0st's machine, the game loaded in about 6 minutes unpatched, 4 minutes 30 seconds with only the duplicate check patched, 2 minutes 50 seconds with only the parser patched, and 1 minute 50 seconds with both patched.</p></site-demo><p>What makes this worth knowing as a backend or full-stack developer isn't the GTA trivia — it's how invisible this class of bug is from the inside. Nobody at Rockstar set out to write an O(n²) parser; it's almost certainly code that was correct and fast when the data was small, and nobody revisited the algorithm as the data grew by orders of magnitude over years of live updates. That's a very normal way for performance bugs to form: not a bad decision at the time, but a reasonable one that stopped being reasonable as scale changed underneath it, with no single commit that &quot;caused&quot; it.</p><p>The practical takeaway scales down easily: when something that used to feel instant has quietly gotten slow, profile it before assuming it's infrastructure. A JSON parse, a config reload, a &quot;just loop through and check&quot; validation step — these are exactly the places where a small, forgotten loop ends up owning your latency budget as your own data grows.</p><p><strong>Sources: </strong><a href="https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/" target="_blank" rel="noopener noreferrer">How I cut GTA Online loading times by 70%</a> · <a href="https://gigazine.net/gsc_news/en/20210302-hacker-reduces-gta-online-load-times" target="_blank" rel="noopener noreferrer">Hackers Succeeded in Reducing Load Time in Grand Theft Auto: Online by 70%</a> · <a href="https://hothardware.com/news/gta-online-ridiculous-load-times-fixed" target="_blank" rel="noopener noreferrer">Hey Rockstar, GTA Online Has A Bad CPU Bottleneck That Slows Loading But There's A Fix</a></p>]]></content:encoded>
    </item>
    <item>
      <title>The unique constraint trap in reordering logic (and how DEFERRABLE constraints fix it)</title>
      <link>https://fanismahmalat.com/blog/deferrable-constraints-reordering-trap/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/deferrable-constraints-reordering-trap/</guid>
      <pubDate>Mon, 21 Sep 2026 09:21:16 GMT</pubDate>
      <description>A plain unique constraint can make swapping or reordering rows fail, even in a transaction. See how PostgreSQL DEFERRABLE constraints fix it, with a live demo.</description>
      <content:encoded><![CDATA[<p>Say you have a <code>rules</code> table with a unique index on (<code>shopId</code>, <code>priority</code>), plus a reorder feature in the UI:</p><blockquote><p><code>shopId | priority | name</code></p><p><code>101    | 1        | Rule A</code></p><p><code>101    | 2        | Rule B</code></p><p><code>101    | 3        | Rule C</code></p></blockquote><p>Two requirements, both non-negotiable: (1) shopId + priority stays unique, and (2) users can reorder rules. Try to satisfy both with the obvious approach and you'll hit a wall fast.</p><h2>Why the simple update fails</h2><p>You want to swap Rule A and Rule B — Rule B moves to priority 1, Rule A moves to priority 2. Wrap it in a transaction and it still fails:</p><blockquote><p><code>BEGIN;</code></p><p><code>UPDATE rules SET priority = 1 WHERE name = 'Rule B';</code></p><p><code>-- At this point, two rows have priority = 1</code></p><p><code>-- DB throws &quot;Unique Constraint Violation&quot;</code></p><p><code>UPDATE rules SET priority = 2 WHERE name = 'Rule A';</code></p><p><code>COMMIT;</code></p></blockquote><p>A regular unique index checks each row the moment it changes — it cannot temporarily bypass the constraint, so the data has to be valid at every single step, not just at the end. The transaction wrapper doesn't help, because the transaction was never the problem; the ordering of statements inside it was.</p><p>The other obvious move — dropping the index and validating uniqueness in application code — trades this problem for a worse one. Now nothing stops two rows from actually ending up with the same priority if two requests race, or if someone touches the table directly. You've satisfied &quot;reorder works&quot; by giving up &quot;uniqueness is guaranteed.&quot;</p><h2>The fix: DEFERRABLE constraints</h2><p>PostgreSQL lets you mark a constraint <code>DEFERRABLE</code>, which lets the uniqueness check wait until COMMIT instead of running as each row changes:</p><blockquote><p><code>ALTER TABLE public.rules</code></p><p><code>ADD CONSTRAINT &quot;UQ_ShopId_Priority&quot;</code></p><p><code>UNIQUE (&quot;ShopId&quot;, &quot;Priority&quot;)</code></p><p><code>DEFERRABLE INITIALLY DEFERRED;</code></p></blockquote><p>With that in place, the exact same swap transaction just works:</p><blockquote><p><code>BEGIN;</code></p><p><code>UPDATE rules SET priority = 1 WHERE name = 'Rule B';</code></p><p><code>-- DB does NOT throw &quot;Unique Constraint Violation&quot; — the constraint is deferred</code></p><p><code>UPDATE rules SET priority = 2 WHERE name = 'Rule A';</code></p><p><code>COMMIT;</code></p><p><code>-- Uniqueness is validated right here, against the final state</code></p></blockquote><p>Nothing about the guarantee changed. The constraint still gets enforced, and the transaction still fails if the final state has a duplicate — you've only moved when it checks, from &quot;as each row changes&quot; to &quot;once, against the state that actually matters.&quot;</p><h2>IMMEDIATE vs. DEFERRED</h2><p>A deferrable constraint gives you two modes, and you can flip between them mid-transaction with <code>SET CONSTRAINTS</code>:</p><ul><li><p>A plain UNIQUE index/constraint (no DEFERRABLE at all) checks each row as it changes, permanently — you cannot change this per-transaction.</p></li><li><p>DEFERRABLE INITIALLY IMMEDIATE checks at the end of each statement by default, which is later than a plain constraint, and any transaction can flip it to DEFERRED on the fly with a command.</p></li><li><p>DEFERRABLE INITIALLY DEFERRED checks at COMMIT time by default, and any transaction can flip it back to IMMEDIATE the same way.</p></li></ul><site-demo component="deferrable-constraints" label="When PostgreSQL checks a unique constraint"><p class="demo-fallback">A NOT DEFERRABLE unique constraint is checked as each row changes, so swapping two rules fails on the first update, and shifting every rule down can fail partway through a single statement. DEFERRABLE INITIALLY IMMEDIATE checks at the end of each statement, so the shift passes but the two-step swap still fails. DEFERRABLE INITIALLY DEFERRED checks at COMMIT, so the swap passes, and a duplicate that is still there at COMMIT fails the whole transaction.</p></site-demo><p>For a table whose writes are mostly ordinary single-row inserts and updates, INITIALLY IMMEDIATE is the safer default — you opt into deferred checking only for the transactions that need it. INITIALLY DEFERRED makes more sense when swaps and reorders are most of what happens to that table anyway.</p><h2>Where this beats the usual workarounds</h2><p>The two workarounds people reach for instead — and where they fall short, especially once an ORM like EF Core is in the picture:</p><ul><li><p>The temp-value hack — move a row to a dummy value like <code>priority = -1</code> to free up the slot, then move it again. It works, but it's 3 updates instead of 2, and now your application logic has to know about a hack that has nothing to do with the actual business rule.</p></li><li><p>The single-statement shift — shove an entire range with one UPDATE, e.g. <code>SET priority = priority + 1 WHERE priority &gt;= X</code>. On a plain unique constraint this is less clean than it looks: rows are updated one at a time in storage order, so the shift can hit a duplicate partway through and fail. It needs a DEFERRABLE constraint, and even INITIALLY IMMEDIATE is enough, because the check waits for the end of the statement. It also only works for continuous blocks — inserting into the middle of a list. An arbitrary swap between two non-adjacent rows isn't a range shift, and this approach doesn't cover it.</p></li></ul><p>DEFERRABLE isn't a replacement for either workaround in every case — it's specifically better for the case both of them handle badly: an arbitrary multi-row swap that needs to pass through a temporarily invalid state on the way to a valid one.</p><h2>Key takeaways</h2><ul><li><p>When to use it: complex, multi-statement transactions that need to pass through a temporarily invalid state — reorders, swaps, anything in that shape.</p></li><li><p>Performance: deferrable uniqueness checks cost more than plain ones, and PostgreSQL's docs warn they can be significantly slower. Worth weighing on a high-throughput table — this is a tool for specific swap/reorder cases, not a default for every unique constraint.</p></li><li><p>Data integrity: unchanged. The constraint still guarantees no duplicates exist once the transaction finishes — you've only changed when it's checked, not whether.</p></li><li><p>Compatibility: standard SQL, but support is split — Postgres and Oracle have it; MySQL and SQL Server don't.</p></li><li><p>Upserts: a deferrable constraint can't be the target of INSERT ... ON CONFLICT, so PostgreSQL rejects upserts that rely on it.</p></li></ul><hr /><p>Thanks to <a href="https://www.linkedin.com/in/angelosconstantinides/" target="_blank" rel="noopener noreferrer">Angelos Constantinides</a> for the discussion that sharpened this one.</p><p><a href="https://lnkd.in/p/dAdmw_Bn" target="_blank" rel="noopener noreferrer">Read the presentation deck on LinkedIn</a></p>]]></content:encoded>
    </item>
    <item>
      <title>Anthropic's 2026 report: engineers are shifting from writing code to orchestrating agents</title>
      <link>https://fanismahmalat.com/blog/anthropic-2026-agentic-coding-report/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/anthropic-2026-agentic-coding-report/</guid>
      <pubDate>Mon, 21 Sep 2026 05:49:06 GMT</pubDate>
      <description>Engineers use AI in about 60% of their work but fully delegate only 0-20%. What held up in Anthropic's 2026 Agentic Coding Trends Report, trend by trend.</description>
      <content:encoded><![CDATA[<p>&quot;Software development is shifting from writing code to orchestrating agents that write code.&quot; That's the framing Anthropic leads with in its 2026 Agentic Coding Trends Report, and the report backs it up with a stat that's more useful than the headline itself: engineers now use AI in roughly 60% of their work, but report fully delegating only 0-20% of tasks without supervision. In other words, the shift isn't from coding to not-coding — it's from typing to reviewing, and the gap between those two is where most of the interesting problems live. I read through the report looking for what's real versus what's marketing, and here's what actually held up, trend by trend.</p><h2>1. The development lifecycle itself is changing shape</h2><p>The report calls this a &quot;tectonic shift&quot;: tactical work — writing, debugging, maintaining code — moves to AI agents, while engineers spend more of their time on architecture and system design. Onboarding timelines reportedly collapse from weeks to hours in some organizations, which changes how teams staff projects dynamically. The concrete example that stood out: Augment Code helped an enterprise customer finish a 4-to-8-month project in two weeks. That's the kind of number that should make you ask &quot;under what conditions,&quot; not just take at face value — but it's a useful upper bound for what's now possible.</p><h2>2. Single agents are turning into coordinated teams</h2><p>Instead of one agent doing everything in sequence, organizations are running multiple specialized agents in parallel under an orchestrator — each with its own context window and role. The report's example here is Fountain, which used hierarchical multi-agent orchestration to get 50% faster candidate screening, 40% quicker onboarding, and cut fulfillment-center staffing time from over a week to under 72 hours. The skill this demands — breaking a task down so separate agents can work on pieces of it without stepping on each other — is basically a new discipline, closer to distributed-systems design than prompt-writing.</p><h2>3. Agents are running for hours, not minutes</h2><p>This is the trend with the number that actually made me stop and re-read it: Rakuten used Claude Code to implement activation vector extraction inside vLLM — a 12.5-million-line codebase — in seven autonomous hours, with 99.9% numerical accuracy. That's not a toy benchmark; that's a real, large, unfamiliar codebase. The broader point is that task horizons are expanding from minutes to days or weeks of autonomous work, which means agents increasingly have to plan, iterate, recover from their own errors, and hold onto project context the whole way through — not just produce one clean diff and stop.</p><h2>4. Oversight doesn't disappear — it scales differently</h2><p>Back to that 60%-usage-but-0-to-20%-delegation number: the report is explicit that effective AI collaboration still requires active human judgment, and that agents are increasingly used to review other agents' output for security and architectural consistency — a second AI reading the first AI's homework, with a human still deciding what matters. CRED's case study frames this well: they doubled execution speed across their whole development lifecycle not by removing developers from the loop, but by shifting them toward higher-value review and design work. That distinction — speed from reallocation, not from absence — is the part I think gets lost when people summarize this report as &quot;AI writes the code now.&quot;</p><h2>5 &amp; 7. Coding is leaking out past engineering entirely</h2><p>Two of the report's trends are really one story: agentic coding is reaching legacy languages (COBOL, Fortran) and domain-specific tooling, and non-engineers — sales, legal, marketing, operations — are building their own automations directly. Zapier's number is the standout: 89% AI adoption across the entire organization, with 800-plus internal AI agents deployed, and design teams reportedly using Claude artifacts to prototype live during customer interviews instead of coming back weeks later with mockups. Legora's case study points the same direction from the other side — they use Claude Code to build their own product, and expose agentic capabilities so lawyers can build sophisticated automations with no engineering background at all. The line between &quot;people who code&quot; and &quot;people who don't&quot; is getting genuinely blurry.</p><h2>6. The economics change what gets greenlit</h2><p>TELUS's numbers are the clearest illustration: 13,000+ custom AI solutions built internally, engineering code shipped 30% faster, and over 500,000 hours saved at an average of 40 minutes per AI interaction. The report frames this as three compounding multipliers — agent capability, orchestration quality, and human experience using the tools — stacking into step-function gains rather than incremental ones. One stat worth sitting with: about 27% of AI-assisted work is described as things that simply wouldn't have gotten done otherwise — exploratory work and small quality-of-life improvements that used to lose the prioritization fight. That's a real, if less flashy, source of value.</p><h2>8. The same tools cut both ways on security</h2><p>The report doesn't dodge this: agentic tools democratize security review and hardening for defenders, but the same capability lowers the bar for reconnaissance and exploit development on the offensive side. Its stated conclusion is that teams who bake agentic security review into their process from the start end up better positioned — not because the tools favor defense inherently, but because defenders who adopt early get the head start.</p><h2>What this looks like on a 3-person team</h2><p>None of the case studies above are a 3-person shop, which is exactly why the report's core tension — 60% usage, 0-20% real delegation — matters more here, not less. There's no second reviewer with spare context to catch a mistake; often it's just me, reviewing my own intern's agent-assisted work, or my own. So in practice: boilerplate, mechanical refactors, and first-draft docs go to an agent without a second thought. Anything touching auth, payments, concurrency, or an architecture decision gets read line by line, no exceptions — and with the intern, that category gets a hand-written first pass before an agent ever touches it, so the reps that build judgment don't get skipped. The test I use before merging anything agent-written: if this breaks at 2am, will I know why within five minutes? If not, that's the review I skipped, and it's cheaper to do now than at 2am.</p><hr /><p>Sources: <a href="https://resources.anthropic.com/2026-agentic-coding-trends-report" target="_blank" rel="noopener noreferrer">2026 Agentic Coding Trends Report</a> (Anthropic), <a href="https://tessl.io/blog/8-trends-shaping-software-engineering-in-2026-according-to-anthropics-agentic-coding-report" target="_blank" rel="noopener noreferrer">8 trends shaping software engineering in 2026</a> (Tessl).</p>]]></content:encoded>
    </item>
    <item>
      <title>Should your team upgrade to TypeScript 7.0 yet? A practical migration checklist</title>
      <link>https://fanismahmalat.com/blog/typescript-7-migration-checklist/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/typescript-7-migration-checklist/</guid>
      <pubDate>Thu, 17 Sep 2026 17:53:08 GMT</pubDate>
      <description>TypeScript 7.0 builds up to 11.9x faster, but tools like typescript-eslint must wait for the 7.1 API. A checklist for deciding whether your team can upgrade yet.</description>
      <content:encoded><![CDATA[<p>TypeScript 7.0 shipped with a full rewrite of the compiler in Go, and the headline numbers are real — but whether your team should upgrade this week depends almost entirely on your toolchain, not on how fast the compiler got. Here's the practical version of that decision.</p><h2>What actually changed under the hood</h2><p>The new native compiler (internally called <code>tsgo</code>) keeps the original compiler's structure and logic, just ported to Go instead of JavaScript. Microsoft's own numbers put build-speed gains at 7.7x to 11.9x across their test projects — VS Code's own compile dropped from 125.7 seconds to 10.6 seconds — with memory usage down 6% to 26% depending on the codebase.</p><p>Two smaller changes matter more for day-to-day migration work. <code>target: es5</code> is gone — not deprecated, gone — and now throws a hard error with no fallback. And the <code>types</code> compiler option now defaults to an empty array instead of implicitly pulling in every <code>@types</code> package in your project. If something in your code relies on ambient globals from a types package you never import directly, it'll quietly stop resolving until you list it explicitly, e.g. <code>&quot;types&quot;: [&quot;node&quot;]</code>.</p><h2>The real blocker: no programmatic API until 7.1</h2><p>This is the part that actually decides whether you can upgrade today. Tools like <code>typescript-eslint</code>, <code>@vue/compiler-sfc</code>, <code>@angular/compiler-cli</code>, and <code>svelte-preprocess</code> don't just call the TypeScript compiler — they hook into its internals (<code>ts.createProgram</code>, <code>ts.transform</code>, <code>ts.factory</code>) to parse and rewrite AST nodes. TypeScript 7.0 shipped without that Programmatic Compiler API entirely — it wasn't trimmed down, it just isn't there yet — so any tool built on top of TypeScript's internals, not just &quot;TypeScript the language,&quot; is locked out until it comes back. Framework maintainers have responded by pinning strict peer-dependency ranges rather than risk a broken build. Microsoft has said 7.1 is expected to restore a stable version of that API.</p><h2>A compatibility checklist before you touch tsconfig</h2><ul><li><p>Check whether your team relies on <code>typescript-eslint</code>, Vue SFC compilation, Angular's compiler-cli, or Svelte preprocessing — all currently blocked pending the 7.1 API.</p></li><li><p>Grep every <code>tsconfig.json</code> in the repo for <code>target: es5</code> or lower — it's now a hard build error, not a warning.</p></li><li><p>Look for code that depends on ambient <code>@types</code> globals from a package you never import directly — it'll need to be added explicitly to <code>types</code> now.</p></li><li><p>Check <code>ts-jest</code>, <code>ts-morph</code>, or anything else with <code>typescript</code> as a peer dependency for a version range that actually covers 7.0.</p></li><li><p>Confirm your editor's TypeScript language-service plugin supports 7.0 — some IDE integrations lag a version behind.</p></li></ul><h2>Benchmark your own build times before you commit to it</h2><p>The published numbers are from Microsoft's own test matrix — worth confirming on your actual codebase before treating them as a given. Node 26.9 introduced a <code>node:bench</code> module, but it still sits behind an <code>--experimental-bench</code> flag, so for now a plain timed build is the more reliable measure: record your current build time (<code>time npx tsc --noEmit</code>, or your actual build command), then re-run it after upgrading in a branch — twice each, so a cold cache doesn't skew the comparison.</p><h2>Recommended rollout path for a small team</h2><ol><li><p>Pin your current TypeScript version in <code>package.json</code> and leave <code>main</code> alone for now.</p></li><li><p>Test the upgrade in an isolated branch, run the checklist above, and see what actually breaks rather than guessing.</p></li><li><p>If nothing in your toolchain touches the Programmatic Compiler API, the upgrade is likely safe to merge once your own benchmark confirms the win.</p></li><li><p>If you do lean on ESLint through <code>typescript-eslint</code>, Vue, Angular, or Svelte tooling, it's simpler to hold off and revisit once 7.1 ships the API — working around it with an older TypeScript pinned just for those tools isn't worth the complexity for a small team.</p></li></ol><p>This is the exact checklist I'll be running before touching TypeScript in a shared repo — and probably sitting on 6.x a little longer for anything that leans on <code>typescript-eslint</code>.</p><hr /><p>Sources: <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/" target="_blank" rel="noopener noreferrer">Announcing TypeScript 7.0</a>, <a href="https://dev.to/the-modern-web/why-angular-vue-and-eslint-cant-upgrade-to-typescript-70-yet-and-why-ts-71-changes-441g" target="_blank" rel="noopener noreferrer">Why Angular, Vue, and ESLint can't upgrade yet</a>, <a href="https://nodejs.org/en/blog/release/v26.9.0" target="_blank" rel="noopener noreferrer">Node.js 26.9.0 release notes</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Volunteer work for Roberts Dog Sanctuary</title>
      <link>https://fanismahmalat.com/blog/voluntarily-work-for-roberts-dog-sanctuary/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/voluntarily-work-for-roberts-dog-sanctuary/</guid>
      <pubDate>Mon, 29 Jun 2020 12:11:23 GMT</pubDate>
      <description>How a university group project became a website for Roberts Dog Sanctuary, built so people can browse the shelter's dogs and request an adoption online.</description>
      <content:encoded><![CDATA[<p>That feeling you get when you see the gratitude in their eyes - that is all you need to keep going.</p><p>Being a volunteer is one of the greatest and most important characteristics in a person. Helping others, and especially animals - that are helpless - is a big accomplishment and takes courage.</p><p>During my 3rd year at the university, I had the honor to be part of a group project on creating a website for a shelter called Roberts Dog Sanctuary. The course we were having was Web Development I and the teacher assigned us different real world projects with real clients. We had the good luck to be assigned to a dog shelter.</p><p>We met with the director of the shelter, Dagmar, a really nice lady - and most importantly an animal lover - to discuss their needs and requirements for the website.</p><p>She told us that the shelter is mostly re-homing dogs abroad, thus their major issue was to find a way to simplify the process of adoption. They wanted to have all the dogs on the website so people could easily see the available dogs. Moreover, when a person would like to request or adopt a dog, they should have been able to fill out a form and submit it through the website.</p><p>Having that in mind, along with other minor requirements, our team started working on it. We began with analyzing the requirements and brainstorming the ideas. Thereafter, we proceeded with the design. Unfortunately, during that semester, we only created a prototype of the website to be continued on the next semester.</p><p>January 2020. This semester we were going to build a fully functional website based on the prototype we created the previous semester. We were all too excited for this project. As time went by, we put together a really nice website and at the end of the semester we delivered a fully functional website.</p><p>Dagmar and the volunteers at the shelter were very happy and grateful for this work, so they wanted us to migrate the website to a new host - their own hosting provider - and make it live.</p><p>Here we are now, in the process of migrating to a new host and preparing the website to go live!</p><p>We are very happy and grateful to be given such opportunity and we hope all our little friends (dogs) find their forever home!</p><p>If you are interested in adopting a dog, feel free to contact the shelter on social media or on the new website which is coming soon!</p><p>Facebook: <a href="https://www.facebook.com/adoptadogcyprus" target="_blank" rel="noopener noreferrer">https://www.facebook.com/adoptadogcyprus</a></p><p>Instagram: <a href="https://www.instagram.com/robertsdogsanctuary/" target="_blank" rel="noopener noreferrer">https://www.instagram.com/robertsdogsanctuary/</a></p><p>Stay tuned, stay positive and stay safe!</p>]]></content:encoded>
    </item>
    <item>
      <title>Welcome to my blog!</title>
      <link>https://fanismahmalat.com/blog/welcome-to-my-blog/</link>
      <guid isPermaLink="true">https://fanismahmalat.com/blog/welcome-to-my-blog/</guid>
      <pubDate>Sat, 16 May 2020 11:36:41 GMT</pubDate>
      <description>Welcome to my blog, where I share my coding journey along with tips and tricks to make programming easier.</description>
      <content:encoded><![CDATA[<p>Welocome!</p><p>This blog is all about connecting with you! I would like to thank you for reading this post and promise you that I am going to share my coding journey with you along with tips and tricks to easier programming!</p><p>Keep positive! Keep coding!</p>]]></content:encoded>
    </item>
  </channel>
</rss>
