<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged developer-experience</title>
<link>https://coderlegion.com/tag/developer-experience</link>
<description>Powered by Question2Answer</description>
<item>
<title>What even is a developer experience audit?</title>
<link>https://coderlegion.com/32861/what-even-is-a-developer-experience-audit</link>
<description>&lt;p&gt;Since getting into the Developer Relations space, I take onboarding to new companies as an opportunity to evaluate what the overall product experience is like. This comes with all sorts of fun things like identifying broken links to fully broken internal processes. And everything in between&lt;/p&gt;
&lt;p&gt;I am a strong believer that everyone is doing their best. I am also rational in understanding that priorities and focus change regularly. I want to make sure I approach this with curiosity and enthusiasm to propose a solution and fix it myself, if that's possible. After all, I probably have less meetings and less responsibilities than everyone else, simply by being &lt;em&gt;new&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If I can't fix it myself, I propose a recommendation. A former manager of mine would be thrilled to see me bringing solutions, not problems. (To be fair to both of us, the platform I supported at the time didn't have a lot of solutions but certainly had a lot of problems.)&lt;/p&gt;
&lt;p&gt;So with a use case in hand, I set off to see what the developer experience was like for Apryse. &lt;/p&gt;
&lt;h3 id=&quot;but-wait-where-does-developer-experience-fit-in-developer-relations-&quot;&gt;But wait, where does Developer Experience fit in Developer Relations?&lt;/h3&gt;
&lt;p&gt;Before we get into the audit details, let's address the DevEx vs. the DevRel elephant in the room. &lt;/p&gt;
&lt;p&gt;For me, Developer Relations is the broader umbrella that encompasses awareness (advocacy), enablement (developer experience), and engagement (community).&lt;/p&gt;
&lt;p&gt;Some companies will bucket this a little different, including the one I'm at now, where Developer Experience is the umbrella but the core focus areas are still largely covered. At Arpyse, we also include AEO under Developer Experience which I think fits nicely in this era of AI-enabled or AI-assisted builders.&lt;/p&gt;
&lt;p&gt;Now let's talk about audit process in terms of awareness, enablement, and engagement. &lt;/p&gt;
&lt;h3 id=&quot;the-process&quot;&gt;The process&lt;/h3&gt;
&lt;p&gt;I want to try to replicate, to the best of my ability, what the &lt;strong&gt;public facing&lt;/strong&gt; developer experience is like. How do developers find the product, how can they try it out, and then how can they (or someone else in the company) start the purchasing and procurement process.&lt;/p&gt;
&lt;p&gt;Doing this during onboarding should be the easiest time to ever do this because you probably have little to no deep product knowledge. you can literally approach it like an external developer because you are one. Unless your onboarding includes product training, which is cheating if it's only available to internals.&lt;/p&gt;
&lt;h4 id=&quot;discovery-awareness-&quot;&gt;Discovery (awareness)&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Can I find the product when I'm looking for it? Is it positioned well against competitors?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In today's world, discovery includes both a traditional search and AI tools, including LLMs like Claude, but also includes AI search summaries, like the ones you see at the top of an otherwise traditional Google search. &lt;/p&gt;
&lt;p&gt;I approach this like any research project I did in school - lots of keywords, questions, and use case based researching. Then I see what kind of results I get.&lt;/p&gt;
&lt;p&gt;If you think only your marketing website or your docs are your entry points for a developer audience, you have some work to do.&lt;/p&gt;
&lt;p&gt;Consider other entry points, like if your product is available on a cloud or platform marketplace, as developers participating in that ecosystem may start there first - think a Salesforce or Appian developer starting in their marketplace rather than accidentally finding something not in the marketplace already and having to build it full custom. Is this information up-to-date? Well maintained and linked back to more granular information across your website and docs? Does it show up on the marketplace search with vague terms or only the company or product name? &lt;/p&gt;
&lt;p&gt;Beyond marketplaces think of entry points like GitHub, package managers, or other places libraries and dependencies already live. Unless I'm starting from scratch or doing a re-platforming effort, the closer you are to where I'm already working, the easier it will be for me to pick it up. And here again, is this information up-to-date, well maintained, and linked back to more granular information? It's not enough to just be listed, it needs to look safe and maintained, often with very limited context.&lt;/p&gt;
&lt;p&gt;Outside the scope of this audit, you can also evaluate broad, general awareness activities typically managed by marketing like events, webinars, blogs, etc. If I go to a developer conference, is Apryse there and how quickly can I understand what the product does in a conference setting?&lt;/p&gt;
&lt;p&gt;All of this is part of the discovery process.&lt;/p&gt;
&lt;p&gt;One thing I should mention before moving on - if your product &lt;em&gt;doesn't&lt;/em&gt; come up when and where you expect it, that's a notable gap. Congrats, you have action items following your audit. To me, awareness gaps are the most critical to address immediately because if people (and AI) can't find you, they can't use you. Fix that!&lt;/p&gt;
&lt;h4 id=&quot;evaluation-amp-trial-enablement-&quot;&gt;Evaluation &amp;amp; trial (enablement)&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;I found the product I think I want to use, but I need to check on the features and eventually build a proof of concept (PoC).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You've been found, either by humans or AI, and they are moving forward with understanding and using your product, often in some kind of trial capacity. &lt;/p&gt;
&lt;p&gt;Before they start building, they need to know if your features and capabilities will solve their problems. Maybe they've seen some marketing pages or blog describing this, but this is where they will start digging for more details.&lt;/p&gt;
&lt;p&gt;This is probably where my process has changed the most in recent years. Using AI tools I can go from discovery to building really fast, sometimes even in the same chat.&lt;/p&gt;
&lt;p&gt;And I did, with several LLMs on basic or free tiers with as little access to memories or prior knowledge as I could possibly give them. I didn't want them to taint my results by knowing I worked for Apryse because I wanted to experience what an external developer would experience, and a bias to Apryse because I was an employee was going to harm that.&lt;/p&gt;
&lt;p&gt;During this evaluation phase is also where I check competitors and even PoC them too. You want to be choosy about this step because it's either time consuming or compute intensive (tokens), but it's a very realistic step for developers evaluating multiple options. &lt;/p&gt;
&lt;p&gt;I'm evaluating the features, the ease of implementation, the clarity of documentation or other assets available for reference, and ultimately how much debugging did I have to do to stand up a working PoC. If this goes smoothly, I will literally go tell all my tech friends about it. (And this should be your goal, to have people outside the company talking about how great it is.)&lt;/p&gt;
&lt;h5 id=&quot;trial-keys-licenses-and-more&quot;&gt;Trial keys, licenses, and more&lt;/h5&gt;
&lt;p&gt;If your product has a trial process, it should be as frictionless as possible. I get that you want leads. You know what I, as an external developer, want? To figure out how many PoCs I have to do before I recommend a solution to my manager on a tight timeline. And know that when I build a PoC, I've timed it so it still works when I have to do a demo.&lt;/p&gt;
&lt;p&gt;Your trial process should be super clear. How many days, weeks, credits, runs is the trial valid for? What features are included or not? Will I see uninformative error messages when using a trial key vs. a production key?&lt;/p&gt;
&lt;p&gt;It does not matter if the friction experienced in this phase is 100% because of how trial keys or licenses are implemented. You need to fix that. External developers shouldn't have to absorb that.&lt;/p&gt;
&lt;h5 id=&quot;a-curated-developer-journey&quot;&gt;A curated developer journey&lt;/h5&gt;
&lt;p&gt;This is the hill I'm willing to die on, sometimes daily. You cannot risk developers or AI defining their own developer journey. You need to own and nurture this. This stage of the audit is where this becomes alarmingly clear. &lt;/p&gt;
&lt;p&gt;Taking a use case, I usually pick a programming language or framework along with it. In this case, I went Javascript (React + Node, with some Vite and Angular sprinkled in along the way. Remember, I ended up doing like 5 or 6 PoCs across Apryse and competitors.) &lt;/p&gt;
&lt;p&gt;With both my own eyeballs and mouse pointer AND, separately, my crew of LLMs, I wanted to see if there was a clear, defined journey through Apryse for a Javascript developer. This was a well supported journey, but not necessarily a curated one depending on where I went or what questions I asked. For example, landing on the marketing website, we don't really link to a developer portal. In fact, we have about 4 pages I would consider someone external to Apryse calling a &quot;developer portal,&quot; so we'll need to fix that.&lt;/p&gt;
&lt;p&gt;A north star goal here would be for my various programming languages or frameworks, a developer in that space can move from discovery to building with clearly identified resources that we nurture, specific to their needs and use cases. For example, a Java developer would probably have different needs and use cases simply by living in the server space (Apryse Server SDK) compared to a developer using WebViewer (Web SDK). This could end up being the same developer, but they are solving different aspects of the problem and need an intro and understanding of our different SDKs. &lt;/p&gt;
&lt;h4 id=&quot;up-and-running-and-beyond-engagement-&quot;&gt;Up and running, and beyond (engagement)&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Is there anyone out there to support me? How can I get deeper knowledge?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once I've successfully built a PoC, or like 5? 6...? in my case, I need to help whoever is actually going to do the purchasing make a final decision to make the purchase happen.&lt;/p&gt;
&lt;p&gt;And in parallel, I need to make sure I know how and where to get support, either paid or community. Ideally, this is open and searchable because I'm both lazy and antisocial. &lt;/p&gt;
&lt;p&gt;This is where I look for community support platforms, forums like Discourse, messaging platforms like Slack or Discord, GitHub communities, etc. I also evaluate their community health - is anyone actually posting? Are the only responses from employees or contributors? Does it look like a place I would feel safe asking a question?&lt;/p&gt;
&lt;p&gt;If there are hundreds or thousands of people silently hanging out, this is a red flag. If only employees or contributors are responding, this is probably a yellow flag or maybe even green if you were looking for free (community) support. As a community grows, the community support should scale with it. If it doesn't, this might be something to look in to.&lt;/p&gt;
&lt;p&gt;I also look at GitHub and forums outside of the direct control of the project or company. What's the sentiment here? Do issues remain open with no responses? Are PRs stale and neglected? Is there a whole subreddit bashing the project?&lt;/p&gt;
&lt;p&gt;At this point, I also start looking for any signs of a champions program or certified developers in this space. This might come up on LinkedIn or a community support forum. These are usually really healthy indicators, that someone external to the company cares enough to go the extra mile with the technology. They may speak at conferences and share their experience too, which loops back to the general awareness activities. &lt;/p&gt;
&lt;p&gt;With this information I can usually wrap up my recommendation and feel confident we are making a good choice.&lt;/p&gt;
&lt;h3 id=&quot;the-caveat&quot;&gt;The caveat&lt;/h3&gt;
&lt;p&gt;It's super important to stay as public and as focused on exactly what a given resource says. If you have access to internal experts or secret resources, this isn't a good replication of what an external user would have access to. If you overcome any and all friction with extensive research, that's not realistic with someone on a development team with a tight sprint timeline. &lt;/p&gt;
&lt;h3 id=&quot;the-ai-caveat&quot;&gt;The AI caveat&lt;/h3&gt;
&lt;p&gt;I took this particular audit as an opportunity to use every LLM and AI chatbot I could reasonably get my hands on - Claude, Microsoft Copilot, GitHub Copilot, Gemini, ChatGPT + Codex. Occasionally I would use them a bit adversarially quoting one to the other. I also wanted to see what kind of project structure they delivered, what kind of experience I had pair programming or debugging with them, and what kind of AI-enabled or assisted experience they delivered.&lt;/p&gt;
&lt;p&gt;Several times I had to reign them in because they could &lt;em&gt;see&lt;/em&gt; my email in my directory file tree or path and infer Apryse as my employer. I get that they are being &quot;smart&quot; but this is not what I wanted. Microsoft Copilot had broad internal access and I actually ended up abandoning it because it would go find internal resources after I would explicitly tell it to only look on the public internet.&lt;/p&gt;
&lt;p&gt;Speaking of public internet, GitHub Copilot out-of-the-box didn't have access to the public internet and neglected to share that with me. I wrote a whole &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/missamarakay/building-pocs-with-codex-and-github-copilot-while-they-try-to-adjust-my-poc-requirements-along-the-1fc9&quot;&gt;thing&lt;/a&gt; about that, but TL;DR it was GUESSING old npm versions before I was finally like CITE YOUR SOURCES and it couldn't... because it didn't have access to the internet. &lt;/p&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;So this was more info into my developer experience audit process. Have you done something similar? What does your process look like?&lt;/p&gt;
&lt;p&gt;Again, and I cannot stress this enough, this isn't a finger pointing exercise. Some things may be accidentally neglected or forgotten, just fix them or recommend a fix and move on. Some things may be missing because it just wasn't the right time to do them, and maybe it's still not the right time. &lt;/p&gt;
&lt;p&gt;The point is you now have a starting point for how and where to improve the developer experience, a great set of talking points to meet folks across the company and start building those cross-functional relationships, and you'll be able to directly impact the external developers trying to build a solution with your product. Lots of points. I think you get the point. 😉&lt;/p&gt;
&lt;p&gt;_Photo by &lt;a rel=&quot;nofollow&quot; href=&quot;https://unsplash.com/@olloweb?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Agence Olloweb&lt;/a&gt; on &lt;a rel=&quot;nofollow&quot; href=&quot;https://unsplash.com/photos/magnifying-glass-near-gray-laptop-computer-d9ILr-dbEdg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;_&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32861/what-even-is-a-developer-experience-audit</guid>
<pubDate>Wed, 28 Oct 2026 15:20:55 +0000</pubDate>
</item>
<item>
<title>Stop Wasting Days on Dev Setup: How We Automated Our Microservices Environment for Every Developer</title>
<link>https://coderlegion.com/28399/stop-wasting-days-dev-setup-how-automated-our-microservices-environment-every-developer</link>
<description>&lt;h2 id=&quot;stop-wasting-days-on-dev-setup-how-we-automated-our-microservices-environment-for-every-developer&quot;&gt;Stop Wasting Days on Dev Setup: How We Automated Our Microservices Environment for Every Developer&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;How we reduced new developer onboarding from 3 days to 15 minutes and eliminated configuration drift across 12 microservices.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-that-made-us-snap&quot;&gt;The Problem That Made Us Snap&lt;/h3&gt;
&lt;p&gt;Picture this: A new senior developer joins your team. They're excited, ready to ship code on day one. Then reality hits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 1:&lt;/strong&gt; They spend 6 hours installing Node, Docker, configuring ESLint to match the team's style, figuring out which environment variables go where, and asking Slack questions like &lt;em&gt;&quot;which version of NestJS are we on again?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 2:&lt;/strong&gt; They finally get &lt;code&gt;user-service&lt;/code&gt; running, but &lt;code&gt;auth-service&lt;/code&gt; throws a CORS error because their local config is slightly different from everyone else's. They spend the afternoon comparing &lt;code&gt;.env&lt;/code&gt; files with a teammate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 3:&lt;/strong&gt; They submit their first PR. The CI pipeline fails because their local Prettier config formats code differently from the shared config that was updated last month — but nobody told them.&lt;/p&gt;
&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;p&gt;We had &lt;strong&gt;12 microservices&lt;/strong&gt;, a team that was growing fast, and a configuration mess that was eating our productivity alive. Every service had slightly different ESLint rules, Docker setups, folder structures, and VS Code settings. Senior developers were spending hours every week answering setup questions instead of building features.&lt;/p&gt;
&lt;p&gt;So we built a system that fixed all of it. Here's exactly how.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-solution-one-repo-to-rule-them-all&quot;&gt;The Solution: One Repo to Rule Them All&lt;/h3&gt;
&lt;p&gt;We created a &lt;strong&gt;central configuration repository&lt;/strong&gt; called &lt;code&gt;dev-environment-config&lt;/code&gt; that acts as the single source of truth for every development environment across our entire microservices ecosystem.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;One repository controls the setup, structure, linting, Docker config, VS Code settings, and shared code for every service. When a senior developer updates it, every developer's machine automatically syncs the next morning.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;the-architecture-at-a-glance&quot;&gt;The Architecture at a Glance&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;dev-environment-config (Central Repo)
├── templates/          → Project scaffolding (NestJS, React)
├── shared/             → ESLint, Prettier, Git configs
├── vscode/             → Extensions &amp;amp; settings
├── docker/             → Base Docker configs
├── scripts/            → Automation (setup, sync, update)
├── common-lib/         → Shared NPM package
└── manifest.json       → Version tracking

         │  syncs to
         ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ user-svc │ │ auth-svc │ │ pay-svc  │  ... 12 services
│ .eslint  │ │ .eslint  │ │ .eslint  │
│ .vscode  │ │ .vscode  │ │ .vscode  │
│ Docker   │ │ Docker   │ │ Docker   │
└──────────┘ └──────────┘ └──────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;implementation-step-by-step&quot;&gt;Implementation: Step by Step&lt;/h3&gt;
&lt;h4 id=&quot;step-1-the-central-config-repository&quot;&gt;Step 1: The Central Config Repository&lt;/h4&gt;
&lt;p&gt;We created a new repository with this structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dev-environment-config/
├── templates/
│   ├── nestjs-service/    # Complete NestJS starter
│   └── reactjs-service/   # Complete React starter
├── shared/
│   ├── .eslintrc.js
│   ├── .prettierrc
│   ├── .editorconfig
│   ├── .gitignore
│   └── commitlint.config.js
├── vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── launch.json
├── scripts/
│   ├── setup-new-developer.sh
│   ├── sync-config.sh
│   ├── auto-update.sh
│   └── create-new-service.sh
├── common-lib/            # Shared NPM package
└── manifest.json          # Version tracker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; is the heartbeat of the system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;version&quot;: &quot;1.2.0&quot;,
  &quot;lastUpdated&quot;: &quot;2024-01-15T10:00:00Z&quot;,
  &quot;updatedBy&quot;: &quot;senior-dev&quot;,
  &quot;configs&quot;: {
    &quot;eslint&quot;: &quot;3.2.0&quot;,
    &quot;prettier&quot;: &quot;2.1.0&quot;,
    &quot;docker&quot;: &quot;1.5.0&quot;
  },
  &quot;services&quot;: [
    &quot;user-service&quot;,
    &quot;auth-service&quot;,
    &quot;payment-service&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time a config changes, we bump the version. This is how the auto-update system knows something changed.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-the-one-command-onboarding-script&quot;&gt;Step 2: The One-Command Onboarding Script&lt;/h4&gt;
&lt;p&gt;This was the game-changer. We wrote a bash script that a new developer runs &lt;strong&gt;once&lt;/strong&gt;, and their entire environment is ready.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The new developer's experience:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install VS Code ✅&lt;/li&gt;
&lt;li&gt;Run one command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sSL https://raw.githubusercontent.com/company/\
dev-environment-config/main/scripts/\
setup-new-developer.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Wait ~10 minutes. Done.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;What the script does under the hood:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -e

WORKSPACE_DIR=&quot;$HOME/company-workspace&quot;
CONFIG_REPO=&quot;https://github.com/company/dev-environment-config.git&quot;

echo &quot;🚀 Setting up your development environment...&quot;

# 1. Check &amp;amp; install prerequisites
check_and_install &quot;git&quot; &quot;Git&quot;
check_and_install &quot;node&quot; &quot;Node.js&quot; &quot;nvm install 20&quot;
check_and_install &quot;docker&quot; &quot;Docker&quot;

# 2. Create workspace &amp;amp; clone everything
mkdir -p &quot;$WORKSPACE_DIR&quot; &amp;amp;&amp;amp; cd &quot;$WORKSPACE_DIR&quot;
git clone &quot;$CONFIG_REPO&quot;

# 3. Clone all service repos from manifest
SERVICES=$(cat dev-environment-config/manifest.json | \
  python3 -c &quot;import sys,json; \
  [print(s) for s in json.load(sys.stdin)['services']]&quot;)

for service in $SERVICES; do
  git clone &quot;https://github.com/company/${service}.git&quot;
done

# 4. Install VS Code extensions
EXTENSIONS=$(cat dev-environment-config/vscode/extensions.json | \
  python3 -c &quot;import sys,json; \
  [print(e) for e in json.load(sys.stdin)['recommendations']]&quot;)

for ext in $EXTENSIONS; do
  code --install-extension &quot;$ext&quot; --force
done

# 5. Sync shared configs to all services
bash dev-environment-config/scripts/sync-config.sh

# 6. Install dependencies everywhere
for service in $SERVICES; do
  cd &quot;$service&quot; &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; cp .env.example .env &amp;amp;&amp;amp; cd ..
done

# 7. Set up daily auto-update cron (8 AM weekdays)
CRON=&quot;0 8 * * 1-5 cd $WORKSPACE_DIR &amp;amp;&amp;amp; \
  bash dev-environment-config/scripts/auto-update.sh&quot;
(crontab -l 2&amp;gt;/dev/null; echo &quot;$CRON&quot;) | crontab -

echo &quot;✅ Setup complete! Open VS Code: code $WORKSPACE_DIR&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. The developer opens VS Code, and everything is configured — extensions, linting, formatting, Docker, environment variables, the works.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-the-config-sync-script&quot;&gt;Step 3: The Config Sync Script&lt;/h4&gt;
&lt;p&gt;This script copies the shared configs from the central repo into every service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# sync-config.sh

CONFIG_ROOT=&quot;$(dirname &quot;$SCRIPT_DIR&quot;)&quot;
WORKSPACE_DIR=&quot;$(dirname &quot;$CONFIG_ROOT&quot;)&quot;

for service in $SERVICES; do
  SERVICE_DIR=&quot;$WORKSPACE_DIR/$service&quot;

  # Copy shared configs
  cp -f &quot;$CONFIG_ROOT/shared/.eslintrc.js&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.prettierrc&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.editorconfig&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.gitignore&quot; &quot;$SERVICE_DIR/&quot;

  # Copy VS Code settings
  mkdir -p &quot;$SERVICE_DIR/.vscode&quot;
  cp -f &quot;$CONFIG_ROOT/vscode/settings.json&quot; &quot;$SERVICE_DIR/.vscode/&quot;
  cp -f &quot;$CONFIG_ROOT/vscode/extensions.json&quot; &quot;$SERVICE_DIR/.vscode/&quot;

  echo &quot;✅ $service synced&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-4-the-daily-auto-update-the-magic-&quot;&gt;Step 4: The Daily Auto-Update (The Magic)&lt;/h4&gt;
&lt;p&gt;This is the part that keeps everything in sync without anyone thinking about it.&lt;/p&gt;
&lt;p&gt;Every morning at 8 AM (weekdays), a cron job runs &lt;code&gt;auto-update.sh&lt;/code&gt; on every developer's machine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# auto-update.sh — Runs daily at 8 AM

cd &quot;$CONFIG_ROOT&quot;

# 1. Check for config updates
OLD_VERSION=$(cat manifest.json | jq -r '.version')
git pull origin main
NEW_VERSION=$(cat manifest.json | jq -r '.version')

if [ &quot;$OLD_VERSION&quot; != &quot;$NEW_VERSION&quot; ]; then
  echo &quot;🆕 Config updated: $OLD_VERSION → $NEW_VERSION&quot;
  bash &quot;$SCRIPT_DIR/sync-config.sh&quot;
fi

# 2. Pull latest code for all services
for service in $SERVICES; do
  cd &quot;$WORKSPACE_DIR/$service&quot;

  # Stash local changes safely
  git stash push -m &quot;auto-update-$(date +%Y%m%d)&quot; 2&amp;gt;/dev/null

  # Pull latest
  git pull origin $(git branch --show-current) --rebase

  # Update dependencies if package.json changed
  if git diff HEAD~1 --name-only | grep -q &quot;package.json&quot;; then
    npm install
  fi

  # Update shared library
  npm update @company/common-lib
done

# 3. Notify the developer
notify-send &quot;Dev Environment&quot; &quot;✅ Updated to config v$NEW_VERSION&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The flow:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Senior Dev pushes config change
         │
         ▼
   GitHub Actions
         │
    ┌────┴────┐
    │ Slack   │ Notify team
    │ Publish │ common-lib to NPM
    └────┬────┘
         │
    Next 8 AM
         │
         ▼
  Every developer's cron runs
         │
    ┌────┴────────────────┐
    │ Pull config repo    │
    │ Sync shared configs │
    │ Pull service repos  │
    │ Update dependencies │
    │ Desktop notification│
    └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-5-the-shared-common-library&quot;&gt;Step 5: The Shared Common Library&lt;/h4&gt;
&lt;p&gt;Instead of copying and pasting guards, interceptors, and DTOs across 12 services, we created &lt;code&gt;@company/common-lib&lt;/code&gt; — a private NPM package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// In any service, just import and use:
import {
  AuthGuard,
  RolesGuard,
  ResponseInterceptor,
  LoggingInterceptor,
  HttpExceptionFilter,
  PaginationDto,
  McpService,        // Qwen MCP integration
} from '@company/common-lib';

@Controller('users')
@UseGuards(AuthGuard, RolesGuard)
@UseInterceptors(ResponseInterceptor)
@UseFilters(HttpExceptionFilter)
export class UserController {
  constructor(private mcpService: McpService) {}

  @Get()
  findAll(@Query() pagination: PaginationDto) {
    return this.userService.findAll(pagination);
  }

  @Post('analyze')
  async analyze(@Body() dto: AnalyzeDto) {
    return this.mcpService.callTool('text-analysis', {
      text: dto.text,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we update the common library (add a new guard, fix a bug in the exception filter), the auto-update system bumps the version in every service automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-6-service-scaffolding&quot;&gt;Step 6: Service Scaffolding&lt;/h4&gt;
&lt;p&gt;Creating a new microservice used to take a day of copy-pasting and tweaking. Now it takes 30 seconds:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash scripts/create-new-service.sh

# Prompts:
# Service name: order-service
# Description: Handles order processing
# Port: 3002
# Type: nestjs

# ✅ Done! Complete service with:
# - src/common/ (filters, guards, interceptors, DTOs)
# - Docker setup (Dockerfile + docker-compose)
# - VS Code settings
# - ESLint + Prettier
# - Health check endpoint
# - Swagger docs
# - MCP integration
# - Auto-update scripts
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-benefits-with-real-numbers-&quot;&gt;The Benefits (With Real Numbers)&lt;/h3&gt;
&lt;h4 id=&quot;1-onboarding-3-days-15-minutes&quot;&gt;1. Onboarding: 3 Days → 15 Minutes&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Manual setup following a 40-step Confluence page &lt;/td&gt;&lt;td&gt;One &lt;code&gt;curl&lt;/code&gt; command&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 days to first productive commit &lt;/td&gt;&lt;td&gt;15 minutes to running all services&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Hey, can you help me with my env vars?&quot; (daily Slack noise) &lt;/td&gt;&lt;td&gt;Zero setup questions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;2-configuration-drift-eliminated&quot;&gt;2. Configuration Drift: Eliminated&lt;/h4&gt;
&lt;p&gt;Before, every service slowly diverged. &lt;code&gt;user-service&lt;/code&gt; had ESLint v8, &lt;code&gt;auth-service&lt;/code&gt; had v7. Prettier configs had subtle differences. Docker setups were copy-pasted and never updated.&lt;/p&gt;
&lt;p&gt;Now, &lt;strong&gt;every service has identical configs&lt;/strong&gt;, and they stay identical because the sync script overwrites them daily.&lt;/p&gt;
&lt;h4 id=&quot;3-senior-developer-time-saved-5-hours-week&quot;&gt;3. Senior Developer Time: Saved ~5 Hours/Week&lt;/h4&gt;
&lt;p&gt;Senior devs were spending roughly an hour a day helping juniors with setup issues, reviewing PRs that failed CI due to formatting, and manually updating configs across services.&lt;/p&gt;
&lt;p&gt;That time is now spent building features.&lt;/p&gt;
&lt;h4 id=&quot;4-consistent-code-quality&quot;&gt;4. Consistent Code Quality&lt;/h4&gt;
&lt;p&gt;Every developer has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The same ESLint rules (auto-fixed on save)&lt;/li&gt;
&lt;li&gt;The same Prettier config (format on save)&lt;/li&gt;
&lt;li&gt;The same commit message format (enforced by Husky)&lt;/li&gt;
&lt;li&gt;The same VS Code extensions (auto-installed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PR reviews went from &lt;em&gt;&quot;please fix your formatting&quot;&lt;/em&gt; to &lt;em&gt;&quot;let's discuss the architecture.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;5-zero-effort-config-updates&quot;&gt;5. Zero-Effort Config Updates&lt;/h4&gt;
&lt;p&gt;When a senior developer wants to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a new ESLint rule → Push to config repo → Everyone gets it tomorrow&lt;/li&gt;
&lt;li&gt;Update Docker base image → Push → Auto-synced&lt;/li&gt;
&lt;li&gt;Add a VS Code extension → Push → Auto-installed&lt;/li&gt;
&lt;li&gt;Fix a bug in the exception filter → Publish common-lib → Auto-updated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No Slack announcements needed. No &quot;please run &lt;code&gt;npm install&lt;/code&gt;&quot; messages. It just happens.&lt;/p&gt;
&lt;h4 id=&quot;6-standardized-project-structure&quot;&gt;6. Standardized Project Structure&lt;/h4&gt;
&lt;p&gt;Every service looks the same. A developer can jump from &lt;code&gt;user-service&lt;/code&gt; to &lt;code&gt;payment-service&lt;/code&gt; and immediately know where everything is. The &lt;code&gt;src/common/&lt;/code&gt; folder has the same patterns. The Docker setup is identical. The health check endpoint is at the same path.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-learned&quot;&gt;What We Learned&lt;/h3&gt;
&lt;h4 id=&quot;start-small&quot;&gt;Start Small&lt;/h4&gt;
&lt;p&gt;We didn't build this all at once. We started with just syncing ESLint and Prettier configs. Once the team saw the value, we added Docker, VS Code settings, the common library, and the auto-update system incrementally.&lt;/p&gt;
&lt;h4 id=&quot;don-t-force-everything&quot;&gt;Don't Force Everything&lt;/h4&gt;
&lt;p&gt;We allow service-specific overrides. If &lt;code&gt;payment-service&lt;/code&gt; needs a special ESLint rule, they can add a &lt;code&gt;.eslintrc.local.js&lt;/code&gt; that extends the shared one. The shared config is the baseline, not a straitjacket.&lt;/p&gt;
&lt;h4 id=&quot;notifications-matter&quot;&gt;Notifications Matter&lt;/h4&gt;
&lt;p&gt;The desktop notification (&quot;✅ Updated to config v1.2.0&quot;) was a small touch, but it made developers &lt;em&gt;aware&lt;/em&gt; of the system. Before that, updates happened silently and people were confused when things changed.&lt;/p&gt;
&lt;h4 id=&quot;version-everything&quot;&gt;Version Everything&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; version tracking was critical. Without it, the auto-update script had no way to know if anything changed, and it would waste time syncing configs that were already up to date.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-complete-stack&quot;&gt;The Complete Stack&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Technology&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Backend &lt;/td&gt;&lt;td&gt;NestJS (TypeScript)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Frontend &lt;/td&gt;&lt;td&gt;ReactJS (TypeScript)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AI &lt;/td&gt;&lt;td&gt;Qwen MCP&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Containers &lt;/td&gt;&lt;td&gt;Docker, Docker Compose&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Runtime &lt;/td&gt;&lt;td&gt;Node.js 20 LTS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database &lt;/td&gt;&lt;td&gt;PostgreSQL 16&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cache &lt;/td&gt;&lt;td&gt;Redis 7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI/CD &lt;/td&gt;&lt;td&gt;GitHub Actions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Editor &lt;/td&gt;&lt;td&gt;VS Code&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Config Sync &lt;/td&gt;&lt;td&gt;Bash scripts + Cron&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;If your team has more than 3 microservices and more than 5 developers, you need a centralized dev environment config. Here's the recipe:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a config repo&lt;/strong&gt; with templates, shared configs, and scripts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write a setup script&lt;/strong&gt; that onboards new devs in one command&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write a sync script&lt;/strong&gt; that copies shared configs to all services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up a cron job&lt;/strong&gt; that auto-updates every morning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a shared NPM library&lt;/strong&gt; for reusable code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build a service generator&lt;/strong&gt; for scaffolding new microservices&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result: developers spend their time writing code, not configuring environments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you solved this problem differently at your company? I'd love to hear about it in the comments.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you found this useful, give it a clap 👏 and follow for more microservices architecture deep-dives.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/28399/stop-wasting-days-dev-setup-how-automated-our-microservices-environment-every-developer</guid>
<pubDate>Thu, 24 Sep 2026 01:52:08 +0000</pubDate>
</item>
<item>
<title>How I Built a Telegram Habit-Tracker Bot with No Programming Experience</title>
<link>https://coderlegion.com/27939/how-i-built-a-telegram-habit-tracker-bot-with-no-programming-experience</link>
<description>&lt;p&gt;Hi everyone!&lt;/p&gt;
&lt;p&gt;This is my first post here. I am writing not for experienced developers, but for those who want to gain their first programming experience using AI models.&lt;/p&gt;
&lt;p&gt;Here is how it went for me.&lt;/p&gt;
&lt;p&gt;Recently, I had no idea about programming and had never built Telegram bots. Three months ago, as an experiment, I started &quot;vibe coding,&quot; and now I have a working Telegram bot with several thousand lines of code, modules for selecting and saving time, passwords, a database, payment processing, 10 languages, and chart generation. You can launch the bot at &lt;a rel=&quot;nofollow&quot; href=&quot;https://telegram.me/I_promise_myself_bot&quot;&gt;https://telegram.me/I_promise_myself_bot&lt;/a&gt; (click the &quot;Try free &amp;amp; Unlock&quot; button).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2162161034962503601&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13140846015101676046&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11338235374769885276&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9176407238152911181&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11104493393799405114&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11143851574970981674&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=18003231601014050572&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9366016401713054251&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I want to share my experience—how I used several different LLMs in a regular free chat, how I bypassed limits, and how I got out of dead ends when the AIs completely stalled, forgot context, and the bot started breaking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where It All Started&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Like everyone else, I started with ChatGPT, then tried Grok, Mistral, Gemini, Qwen, and DeepSeek bit by bit. I wanted to figure out through trial and error what each could do and for how long. I quickly ran into token limits in free chats. Also, over long distances, they lose context at different times, start mixing up file structures, and produce non-working pieces of code.&lt;/p&gt;
&lt;p&gt;As long as there were 200-300 lines of code, working with AIs was manageable, but when the code exceeded 1,000 lines, the problems began. To overcome bottlenecks, I couldn't paste or upload such an amount of code into the window for the AI to see the whole code, find the exact error, or add a new feature.&lt;/p&gt;
&lt;p&gt;Another problem was that any AI, as a rule, forgets the conversation after a few hours or starts hallucinating heavily. After that, you have to kill the chat and create a new one, re-supplying the new chat with the general task, snippets of chat history and code, and almost the entire conversation so it understands what I want from it. I regularly read bloggers, and regarding this, I asked Kostya Doronin for advice on how to work with AIs when code accumulates to more than 1,000 lines across multiple files. Surprisingly, he readily suggested several free and paid workarounds, but I managed with free ones only. I fundamentally wanted to figure out if it's possible to make something working with AIs for free.&lt;/p&gt;
&lt;p&gt;I had to change my approach a bit. First, I roughly figured out what can be squeezed out of this or that AI, when they start glitching, and how to bypass it. I stopped loading one AI with all tasks at once and started dividing tasks among models.&lt;/p&gt;
&lt;p&gt;I settled on these:&lt;/p&gt;
&lt;p&gt;1.Claude is the architect. It has the best logic and understanding of the general code and project structure. I used it for brainstorming and design, modularizing, and creating step-by-step instructions for other AIs. Also as a &quot;rescuer&quot; in dead-end situations when another AI started leading me in circles. The only downside is that free tokens run out very quickly, and you have to wait a few hours.&lt;br&gt;
2.Qwen turned out to be the main &quot;workhorse&quot; over long distances. If you give it a detailed plan from Claude, Qwen handles it well and sticks to the plan. Claude has strict limits on free messages, but with Qwen, you could code all day long in the same chat, solving tasks one step at a time. It holds up for about 7 days before hallucinations begin, after which you need to kill the chat and start a new one.&lt;br&gt;
3.Gemini is an additional tool for quickly double-checking individual pieces of code, like a &quot;fresh perspective&quot; on a problem or a new task. But in the free version, it is not particularly good at programming and architecture, and if you work with it for a whole day or two, it also starts hallucinating. Although there were periods when these same models worked for weeks without any hallucinations at all. And sometimes a day was enough for both Qwen and Gemini to start glitching, which I never encountered with Claude. Probably because it ran out quickly! From which I concluded that model owners for some reason release different quality versions of the same model into the wild.&lt;/p&gt;
&lt;p&gt;In other words, all these LLMs are a very unreliable thing, and for serious projects, you should only work with people. But as assistants in simple coding tasks, they are very good.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Project Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At the beginning of creating the bot, Claude guided me step-by-step to download and set up all these tools on my computer:&lt;/p&gt;
&lt;p&gt;•Language and framework: Python 3.11+, aiogram 3.x.&lt;br&gt;
•Database: Started with simple Google Sheets, but eventually switched to PostgreSQL, hosted on Supabase. Mistake: should have used PostgreSQL from the very beginning.&lt;br&gt;
•Background tasks: APScheduler for push notifications.&lt;br&gt;
•Graphics: matplotlib for reports.&lt;br&gt;
•Payments: Paddle SDK integration.&lt;br&gt;
•Localization: Multilingual system based on JSON (languages). Mistake: did it at the very end, but should have done it at the very beginning because I had to redo a lot of things.&lt;br&gt;
•Security: Isolating tokens in a separate file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Development Process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For each new bot feature, I tasked Claude with developing as detailed a step-by-step instruction plan for Qwen as possible. I coded in VS Code, but didn't use LLMs inside it directly, only in chats:&lt;/p&gt;
&lt;p&gt;1.I initially split the entire project into logical modules: there is one main file up to 2,000 lines, and the rest are 200-700 lines. Everything is separate – handlers, database operations, notifications, buttons, and localization.&lt;br&gt;
2.Working with Qwen: Before any changes, I saved all recent files with working code in a separate folder so I could easily roll back. I gave Qwen a step-by-step plan and changed or added functionality step by step. I inserted it into the project, ran the bot, and checked the operation and logs. If there was a problem, I gave the logs to all three AIs at once to find the issue. If the problem hit a dead end, I started over from the last working code, but mentioned in the task to Claude (if tokens remained) that there were such-and-such problems during the previous change and they needed to be avoided. If Claude was exhausted, I asked Gemini. And so I moved step by step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How I Got Out of Dead Ends&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Somewhere in the middle of the project, the code exceeded a thousand lines, and problems began: the AI forgot the context and broke what was already working when trying to fix a bug.&lt;/p&gt;
&lt;p&gt;At that moment, I brought in Claude:&lt;/p&gt;
&lt;p&gt;1.I described the essence of the technical problem and the bot's startup logs in VS Code.&lt;br&gt;
2.I provided snippets of chat history where Qwen started going in circles.&lt;br&gt;
3.I attached the problematic code.&lt;/p&gt;
&lt;p&gt;Mostly, Claude found the error on the first try and provided a solution. If it didn't know, it asked leading questions and we ran additional checks. Unlike Qwen or Gemini, which immediately &quot;poured out&quot; a bunch of possible causes after which going in infinite circles began. In especially neglected cases, I &quot;pitted models against each other&quot;: I drove the problematic piece in circles between Claude, Gemini, and Qwen until one of them found the bug. Their collective intelligence works much more reliably.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After a few months of casual work, I got a working Telegram bot with several thousand lines of code, written from scratch with no experience.&lt;/p&gt;
&lt;p&gt;What I realized for myself:&lt;/p&gt;
&lt;p&gt;1.On simple tasks, AI replaces a team, but you need to understand the project structure well yourself and at least superficially understand the code. If you have no knowledge, you can learn to think systemically and architecturally on such projects.&lt;br&gt;
2.Use different models. Using a single AI for everything is undesirable. The &quot;architect + coder + fresh perspective&quot; bundle works better.&lt;br&gt;
3.Small steps. Wrote a function – tested it. Broke – rolled back to the last working version.&lt;br&gt;
4.Save chat history. Chat history with AI helps you not to lose the logic of the project yourself after a week or month of work, and to provide context to another AI for problem analysis.&lt;/p&gt;
&lt;p&gt;I will be glad to answer questions in the comments!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27939/how-i-built-a-telegram-habit-tracker-bot-with-no-programming-experience</guid>
<pubDate>Mon, 21 Sep 2026 12:26:13 +0000</pubDate>
</item>
<item>
<title>Your Hiring Process Needs HTTP Status Codes</title>
<link>https://coderlegion.com/24649/your-hiring-process-needs-http-status-codes</link>
<description>&lt;p&gt;&lt;em&gt;Because &quot;we'll be in touch&quot; is not an observable state.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I submitted a job application recently.&lt;/p&gt;
&lt;p&gt;It was one of those applications where you actually put in the effort. I read the job description carefully, tailored the resume, wrote a cover letter specifically for the organization, and answered the optional questions rather than pretending they were optional.&lt;/p&gt;
&lt;p&gt;Then I clicked &lt;strong&gt;Submit&lt;/strong&gt; and got... nothing.&lt;/p&gt;
&lt;p&gt;No confirmation email. No &quot;we received your application.&quot; No indication that a candidate record had been created. Just a browser page telling me the submission had succeeded and the vague hope that somewhere, deep inside an applicant tracking system, my carefully assembled application had not been written directly to &lt;code&gt;/dev/null&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As a developer, I find this troubling. Not because I expect an interview, or because I think every recruiter owes me personalized feedback. I just want a status code.&lt;/p&gt;
&lt;p&gt;And I think that's a smaller ask than it sounds like, for a reason I'll come back to at the end: the states already exist. Somewhere in that ATS, my application is in one. The decision not to show me which one is a product decision, not a privacy constraint.&lt;/p&gt;
&lt;h3 id=&quot;the-web-solved-this-problem-decades-ago&quot;&gt;The Web Solved This Problem Decades Ago&lt;/h3&gt;
&lt;p&gt;When software sends a request to another system, we generally consider it useful for the receiving system to say what happened. Did it work? Is it still processing? Was the request malformed? Is the resource gone? Did something catch fire on the server?&lt;/p&gt;
&lt;p&gt;HTTP gives us an entire vocabulary for this.&lt;/p&gt;
&lt;p&gt;Hiring systems, meanwhile, have developed their own protocol:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST /careers/applications

[no observable response]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is sometimes followed, anywhere from three minutes to six months later, by:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;After careful consideration...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of inventing vague candidate statuses like &quot;Under Consideration,&quot; perhaps hiring systems could adopt something developers already understand.&lt;/p&gt;
&lt;h3 id=&quot;the-2xx-family-success-allegedly&quot;&gt;The 2xx Family: Success, Allegedly&lt;/h3&gt;
&lt;h4 id=&quot;200-ok&quot;&gt;&lt;code&gt;200 OK&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;A human being has reviewed your application.&lt;/p&gt;
&lt;p&gt;Nothing else is implied.&lt;/p&gt;
&lt;p&gt;Honestly, this alone would be revolutionary.&lt;/p&gt;
&lt;h4 id=&quot;202-accepted&quot;&gt;&lt;code&gt;202 Accepted&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Your application has been received and accepted for processing.&lt;/p&gt;
&lt;p&gt;This is the most important status code in the entire hiring protocol. The candidate does not need the hiring manager's notes or a walkthrough of the internal workflow. They need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;202 APPLICATION_ACCEPTED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we know the thing exists.&lt;/p&gt;
&lt;h4 id=&quot;204-no-content&quot;&gt;&lt;code&gt;204 No Content&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Your application was successfully received.&lt;/p&gt;
&lt;p&gt;We have chosen not to acknowledge this in any way.&lt;/p&gt;
&lt;h3 id=&quot;the-3xx-family-you-have-been-redirected&quot;&gt;The 3xx Family: You Have Been Redirected&lt;/h3&gt;
&lt;h4 id=&quot;302-found&quot;&gt;&lt;code&gt;302 Found&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;We found an internal candidate.&lt;/p&gt;
&lt;p&gt;Thanks for playing.&lt;/p&gt;
&lt;h4 id=&quot;304-not-modified&quot;&gt;&lt;code&gt;304 Not Modified&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Your application has been &quot;Under Review&quot; for 47 days.&lt;/p&gt;
&lt;p&gt;There has been no change since the last time you checked, or the 23 times before that. There will be no change the next time either. Please consider caching this response locally.&lt;/p&gt;
&lt;h4 id=&quot;410-gone&quot;&gt;&lt;code&gt;410 Gone&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The position existed when you applied. It no longer exists. No additional information is available.&lt;/p&gt;
&lt;p&gt;At least &lt;code&gt;410&lt;/code&gt; would be useful. The current discovery mechanism is noticing that your bookmarked job posting has started redirecting to the careers homepage.&lt;/p&gt;
&lt;h3 id=&quot;the-4xx-family-this-one-s-on-you&quot;&gt;The 4xx Family: This One's On You&lt;/h3&gt;
&lt;h4 id=&quot;401-unauthorized&quot;&gt;&lt;code&gt;401 Unauthorized&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You must create an account before applying.&lt;/p&gt;
&lt;p&gt;Your password must contain at least 14 characters, uppercase and lowercase letters, a number, a symbol, and one ancient Sumerian glyph. It cannot match any password you have used since 1997.&lt;/p&gt;
&lt;p&gt;You will then manually enter every field from the resume you just uploaded.&lt;/p&gt;
&lt;p&gt;You will never use this account again.&lt;/p&gt;
&lt;h4 id=&quot;403-forbidden&quot;&gt;&lt;code&gt;403 Forbidden&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You have the required experience and qualifications.&lt;/p&gt;
&lt;p&gt;Unfortunately, you live 37 miles outside the geographic boundary within which we have determined this Zoom meeting can occur.&lt;/p&gt;
&lt;h4 id=&quot;404-not-found&quot;&gt;&lt;code&gt;404 Not Found&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Hiring manager not found. Recruiter not found. Job posting not found.&lt;/p&gt;
&lt;p&gt;Nobody knows who owns this requisition.&lt;/p&gt;
&lt;h4 id=&quot;408-request-timeout&quot;&gt;&lt;code&gt;408 Request Timeout&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;After seven weeks without communication, the candidate has accepted another position.&lt;/p&gt;
&lt;p&gt;The hiring team will contact them tomorrow to schedule the second interview.&lt;/p&gt;
&lt;h4 id=&quot;409-conflict&quot;&gt;&lt;code&gt;409 Conflict&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The position requires 12 years of production experience with a technology introduced six years ago.&lt;/p&gt;
&lt;p&gt;The conflict cannot be resolved.&lt;/p&gt;
&lt;h4 id=&quot;417-expectation-failed&quot;&gt;&lt;code&gt;417 Expectation Failed&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You expected the phrase &quot;we'll be in touch&quot; to imply future communication.&lt;/p&gt;
&lt;p&gt;It did not.&lt;/p&gt;
&lt;h4 id=&quot;422-unprocessable-content&quot;&gt;&lt;code&gt;422 Unprocessable Content&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Your experience is relevant, but your career path does not fit cleanly into the predefined boxes in our applicant tracking system.&lt;/p&gt;
&lt;p&gt;Human intervention may be required.&lt;/p&gt;
&lt;p&gt;Human intervention is unavailable.&lt;/p&gt;
&lt;h4 id=&quot;425-too-early&quot;&gt;&lt;code&gt;425 Too Early&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You applied 14 minutes after the job was posted.&lt;/p&gt;
&lt;p&gt;LinkedIn reports 612 applicants.&lt;/p&gt;
&lt;p&gt;We have questions too.&lt;/p&gt;
&lt;h4 id=&quot;451-unavailable-for-legal-reasons&quot;&gt;&lt;code&gt;451 Unavailable For Legal Reasons&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Your qualifications are not the issue.&lt;/p&gt;
&lt;p&gt;The issue is a background check vendor, a non-compete of uncertain enforceability, or a sponsorship question nobody wants to be the one to ask about.&lt;/p&gt;
&lt;h3 id=&quot;the-5xx-family-it-was-never-you&quot;&gt;The 5xx Family: It Was Never You&lt;/h3&gt;
&lt;h4 id=&quot;500-internal-server-error&quot;&gt;&lt;code&gt;500 Internal Server Error&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Something has gone wrong internally. Your recruiter left, the headcount was frozen, the hiring manager changed, Finance reconsidered the budget, the organization restructured, or the position was posted by accident.&lt;/p&gt;
&lt;p&gt;We will communicate all of these possibilities using the same message:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;We have decided to move forward with other candidates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;502-bad-gateway&quot;&gt;&lt;code&gt;502 Bad Gateway&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The recruiter says they are waiting for the hiring manager.&lt;/p&gt;
&lt;p&gt;The hiring manager says recruiting owns the next step.&lt;/p&gt;
&lt;p&gt;Your request cannot be routed.&lt;/p&gt;
&lt;h4 id=&quot;503-service-unavailable&quot;&gt;&lt;code&gt;503 Service Unavailable&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Your recruiter is no longer with the company.&lt;/p&gt;
&lt;p&gt;Their calendar link remains active.&lt;/p&gt;
&lt;h4 id=&quot;504-gateway-timeout&quot;&gt;&lt;code&gt;504 Gateway Timeout&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&quot;We expect to have a decision by Friday.&quot;&lt;/p&gt;
&lt;p&gt;Friday has passed. The specification does not define which Friday was intended.&lt;/p&gt;
&lt;h3 id=&quot;candidates-don-t-need-distributed-tracing&quot;&gt;Candidates Don't Need Distributed Tracing&lt;/h3&gt;
&lt;p&gt;There's a serious point hiding under all of this. Hiring has an observability problem, and it's usually mistaken for a communication problem.&lt;/p&gt;
&lt;p&gt;Companies understandably cannot expose everything happening inside a recruiting process. Candidate evaluations are private. Interview feedback may be sensitive. Hiring managers need room to compare people and make decisions.&lt;/p&gt;
&lt;p&gt;That's fine. Nobody is asking for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /hiring-manager/private-thoughts/about-me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But there is an enormous amount of territory between exposing confidential deliberations and providing no information at all.&lt;/p&gt;
&lt;p&gt;A hiring pipeline already has states. An application was received. It entered review. Someone reviewed it. An interview was requested. The requisition was paused. The position was filled. The position was canceled. The candidate was declined.&lt;/p&gt;
&lt;p&gt;Those state transitions already exist in the system. They are already recorded, timestamped, and reportable, because that's how recruiting teams measure their own funnel. The candidate is the one participant in the process who cannot see the record they're in.&lt;/p&gt;
&lt;h3 id=&quot;give-us-the-boring-version&quot;&gt;Give Us the Boring Version&lt;/h3&gt;
&lt;p&gt;Imagine an applicant portal that reported nothing more than this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;202  APPLICATION_RECEIVED     Mar 04
200  REVIEWED                 Mar 19
304  NO_CHANGE                Apr 02
304  NO_CHANGE                Apr 30
410  POSITION_CLOSED          May 12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's not radical transparency. It's barely transparency at all. But it tells you the system is functioning.&lt;/p&gt;
&lt;p&gt;It removes the need to wonder whether an application disappeared. It reduces pointless portal refreshing. And it prevents candidates from reading silence as information, when silence might mean anything from &quot;the hiring manager is on vacation&quot; to &quot;the requisition was canceled two weeks ago.&quot;&lt;/p&gt;
&lt;p&gt;Mostly, it treats applicants like participants in a process rather than packets transmitted into an undocumented endpoint.&lt;/p&gt;
&lt;h3 id=&quot;200-ok-1&quot;&gt;&lt;code&gt;200 OK&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;I don't expect every application to result in an interview. I've submitted enough of them to have considerable empirical evidence supporting that position.&lt;/p&gt;
&lt;p&gt;I don't expect detailed rejection feedback either. I understand why companies are reluctant to provide it. I don't even particularly mind &lt;code&gt;403 REQUIREMENTS_NOT_MET&lt;/code&gt; or &lt;code&gt;410 POSITION_FILLED&lt;/code&gt;. Those are answers.&lt;/p&gt;
&lt;p&gt;What makes modern hiring uniquely frustrating isn't rejection. It's uncertainty about whether anything is happening at all.&lt;/p&gt;
&lt;p&gt;Software engineers have spent decades building systems around a simple principle: when one system asks another to do something, the response should carry enough information to understand what happened.&lt;/p&gt;
&lt;p&gt;Candidates aren't asking for distributed tracing of your recruiting infrastructure.&lt;/p&gt;
&lt;p&gt;We'd settle for a status code.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24649/your-hiring-process-needs-http-status-codes</guid>
<pubDate>Wed, 26 Aug 2026 15:46:00 +0000</pubDate>
</item>
<item>
<title>Why Octopus invests in developer experience</title>
<link>https://coderlegion.com/21835/why-octopus-invests-in-developer-experience</link>
<description>&lt;p&gt;When you join Octopus, you get a great laptop and your choice from a range of docks, monitors, webcams, and input devices. You also get a home office allowance, which lets you upgrade your space and get 50% back. You may want a sit/stand desk, an awesome chair, and portrait lighting so everyone can see how delighted you are when they demo their cool new feature.&lt;/p&gt;
&lt;p&gt;What we ask in return is that you set yourself up to do your best work. Invest in your &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.hanselman.com/blog/brain-bytes-back-buns-the-programmers-priorities&quot;&gt;brain, bytes, back, and buns&lt;/a&gt;, as Scott Hanselman put it. The essential idea behind this is that when you win, Octopus wins. We all win!&lt;/p&gt;
&lt;p&gt;Yet you can throw a soggy cat at a conference, and the chances are it will splash someone from an organization with a completely different approach to developer experience. One that doesn't value the sustainable delivery of their people's best work. Why is that?&lt;/p&gt;
&lt;h3 id=&quot;a-fork-stuck-in-the-road&quot;&gt;A fork stuck in the road&lt;/h3&gt;
&lt;p&gt;As we've seen in the software industry, silos are the source of all kinds of chaos. DevOps was created when someone realized the scale of the problem caused by having two teams with conflicting goals working on opposite sides of the same problem. Leaders whipped developers up for feature throughput and wanted to hold operations teams to account for reliability.&lt;/p&gt;
&lt;p&gt;In hindsight, it's obvious that this isn't going to work, but we did it for decades. You can get halfway to DevOps just by aligning the goals. When both teams share the same target, they'll work out how to get you the rest of the way.&lt;/p&gt;
&lt;p&gt;If we zoom out on our organization, we're likely to find a similar silo/goal conflict between engineering managers and the finance team. We ask the engineering manager to build a high-performing team, while we direct finance to spend as little money as possible. This structural incentive problem leads developers to get laptops and tools that someone has negotiated to be &quot;good enough,&quot; and trust me, they aren't.&lt;/p&gt;
&lt;p&gt;When you look at developer experience through a FinBoss lens, where finance and engineering managers align around the return on investment, you'll find the optimal spend on developer tools shifts right rapidly.&lt;/p&gt;
&lt;p&gt;FinCost organizations reduce costs through standardization, commoditization, and by delaying kit refreshes for as long as possible. They often spend less than 1,000 USD on a laptop without realizing the true cost of an under-spec'd machine. Meanwhile, FinBoss organizations are seeking the 30,000 USD annual return on a 3,000 USD investment.&lt;/p&gt;
&lt;p&gt;To achieve this ROI, finance and engineering leadership work together to design policies that allow engineers to choose from a set of well-designed options. The combination of investment in great tools and individual choice in building the right setup is the path to success.&lt;/p&gt;
&lt;h3 id=&quot;tiny-visible-costs&quot;&gt;Tiny visible costs&lt;/h3&gt;
&lt;p&gt;FinCost organizations are driving decisions based on small, visible costs. Where there's a paper trail, there are savings to make: the purchase of a laptop, the training budget, and the conference invoice. These are part of the legible model, so there is no guesswork or experimental method needed to cost them. The numbers are right there on the page.&lt;/p&gt;
&lt;p&gt;Once you're managing costs, it's not uncommon to dial up the control, too. You limit options, standardize across the organization, and negotiate in bulk on everything to drive costs even lower. Compared to competitors, you could be spending 75% less on laptops, and just look at the savings from those pesky licenses and SaaS tools various teams were using. Hooray.&lt;/p&gt;
&lt;p&gt;Instead of letting engineers attend conferences they find relevant to their work, the organization negotiates a bulk purchase for a single conference. Instead of teams buzzing with diverse ideas and approaches, they all sit and watch the same talk. It's cheaper that way.&lt;/p&gt;
&lt;p&gt;A wooden fence encloses my garden. A fence lasts a long time if you give it a protective coat of wood preserver every once in a while. A tin of wood preserve costs about 20 USD and a few hours of mindful application each year. Over 5 years, I can save 100 USD by skipping the treatment. If I do this, my fence lasts about 10 years instead of 30.&lt;/p&gt;
&lt;p&gt;The cost of a tin of wood preserver is immediate and visible. The cost of replacing the fence is far higher, but it won't be visible for years. Additionally, if the fence provides value beyond privacy, like keeping a goat out of my vegetable garden, the cost of the fence failing could be the cost of my whole crop, which is even less legible to my cost control mindset.&lt;/p&gt;
&lt;p&gt;If you're creating software, it's likely to be more valuable than a fence. In fact, it should be giving you returns multiple times your investment. It's like a golden goose. You feed it well, and you get regular, valuable deliveries. If you had a golden goose, you wouldn't try to save on food costs if it reduced the flow of golden eggs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11199186589923672688&quot; alt=&quot;A cartoon showing a character trying to save money by reducing the cost of feeding a golden goose. The goose doesn't survive this experiment.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The same logic plays out with training. A FinCost organization cuts the training budget because the line item is visible and the return isn't. Individual engineers stop attending relevant courses, and teams miss out on the steady accumulation of skill that comes from ongoing, targeted learning. Nobody raises a purchase order for &quot;capability that didn't grow this year.&quot;&lt;/p&gt;
&lt;p&gt;Then something goes wrong. Delivery is slow, the codebase is a mess, or a competitor pulls ahead. Someone decides the answer is transformation, and that means Scaled Agile, or whatever flavor of desperation is in fashion in exec suites at the time.&lt;/p&gt;
&lt;p&gt;So the whole department gets booked onto an external SAFe course. Not one or two people who need it. Everyone. The invoice lands, and it's eye-watering: many times what a thoughtful, ongoing training program would have cost across the same period. The organization that was too cautious to spend 500 USD on a course people wanted just spent 50,000 USD on one nobody asked for.&lt;/p&gt;
&lt;p&gt;This is the hidden tax of FinCost thinking. Small, visible costs get cut. The problems that those small costs would have prevented quietly compound. Eventually, the pressure releases all at once, in an expensive, rushed, and hard-to-argue-against way, because now there's a crisis with a visible price tag.&lt;/p&gt;
&lt;p&gt;The tin of wood preserver was the right investment all along.&lt;/p&gt;
&lt;h3 id=&quot;the-invisible-people-hours&quot;&gt;The invisible people-hours&lt;/h3&gt;
&lt;p&gt;Developers work around 40 hours a week. Stripe's Developer Coefficient found that productivity friction can eat up roughly half of those hours. That's the time that evaporates before a single line of useful code is written. Across the global developer workforce, Stripe put a number on that loss: 300 billion USD. It's worth noting that only 85 billion USD of this is attributable to bad code. The rest is pure friction: slow tools, broken environments, waiting for builds, wrestling with under-spec'd machines. Productivity means more than writing code faster.&lt;/p&gt;
&lt;p&gt;That friction has a texture. It's a developer losing flow because their laptop is thrashing memory, and they've lost the thread of what they were thinking.&lt;/p&gt;
&lt;p&gt;It's waiting 10 minutes for a build that would have completed in 2 minutes on a faster machine. It's the workarounds you built because the approved tool wasn't good enough, which obliterates hours of your week for maintenance. It's the skilled engineers who start looking for a new role because the environment makes them feel like you don't take their work seriously.&lt;/p&gt;
&lt;p&gt;None of this appears on a purchase order. None of it shows up in a variance report. It just quietly drains the value your software could be creating.&lt;/p&gt;
&lt;p&gt;There's also an upside case, not just a loss case. Software that ships faster captures more market share. Developers who stay in flow write better code with fewer bugs, which means less rework and fewer incidents. A high-performing team attracts other high performers. The return on investing in developer experience isn't just &quot;we lose less time&quot;; it's sales opportunities, reduced churn, higher product quality, and a compounding advantage over competitors who are still buying 800 USD laptops and calling it responsible.&lt;/p&gt;
&lt;p&gt;When you focus on cost control, there's a ceiling on how much you can save. When you look for return on investment, there's no equivalent ceiling on how much you can gain.&lt;/p&gt;
&lt;h3 id=&quot;the-cfo-lens&quot;&gt;The CFO lens&lt;/h3&gt;
&lt;p&gt;When I asked Sammy, our CFO, about why we invest in people, powerful machines, and great tools, he said: &quot;At Octopus, we think about equipment as a productivity tool. Developers are one of our most expensive and constrained resources. If spending a few extra thousand dollars on a fast machine and proper screen space saves time, reduces cognitive load, or keeps someone in flow more often, it pays for itself very quickly.&quot;&lt;/p&gt;
&lt;p&gt;That means our finance team doesn't ask &quot;What's the cheapest laptop we can buy?&quot; Instead, they ask, &quot;What setup helps this person do their best work?&quot; From a finance perspective, this is about a high-ROI spend, not indulgence.&lt;/p&gt;
&lt;p&gt;Octopus is running the FinBoss playbook, which aligns finance and leadership in empowering employees to do their best work. That means expanding what you're willing to measure so that you can balance the visible costs with traditionally invisible returns.&lt;/p&gt;
&lt;p&gt;The responsibility for providing this environment for employees rests with all leaders, including Sammy, and is part of the deal employees have with the company; we'll give you the best environment and tools if you give us the greatest work of your career.&lt;/p&gt;
&lt;h3 id=&quot;the-reckoning&quot;&gt;The reckoning&lt;/h3&gt;
&lt;p&gt;If we've ever met, I've likely mentioned &lt;a rel=&quot;nofollow&quot; href=&quot;https://dora.dev/research/&quot;&gt;DORA's research&lt;/a&gt;. I'm a super-fan of their State of DevOps reports, because they provide us techniques, practices, and outcomes that are all linked through complex relationships. The model shows that transformational leadership, lean product management, and Continuous Delivery drive software delivery performance and organizational outcomes.&lt;/p&gt;
&lt;p&gt;While many of the capabilities in the DORA model are technical, many others relate to leadership. Particularly relevant to developer experience are generative organizational culture, transformational leadership, empowering teams to choose tools, team experimentation, and a learning culture. You can read about these and more on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://dora.dev/capabilities/&quot;&gt;DORA website&lt;/a&gt;. Together, they back up the FinBoss concept with tens of thousands of survey samples that support the case.&lt;/p&gt;
&lt;p&gt;The question is: Which organization are you now, and which do you want to be? Is it FinCost, operating myopically against the bottom line, or FinBoss, sharing the responsibility for developer experience and employee productivity? The fork in the road was never a choice. We know FinCost is a dead end, and the benefits of FinBoss aren't soft claims; they're measurable outcomes.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21835/why-octopus-invests-in-developer-experience</guid>
<pubDate>Fri, 03 Jul 2026 12:47:04 +0000</pubDate>
</item>
<item>
<title>Minha Experiência com o uso do SpecKit no desenvolvimento em Infra/Cloud</title>
<link>https://coderlegion.com/21410/minha-experiencia-com-o-uso-do-speckit-no-desenvolvimento-em-infra-cloud</link>
<description>&lt;p&gt;A engenharia de plataforma e o DevOps estão passando por uma transformação impulsionada por agentes de IA. Recentemente, em meus estudos de arquitetura, infraestrutura e governança, comecei a utilizar o &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/github/spec-kit&quot;&gt;SpecKit&lt;/a&gt;&lt;/strong&gt;, uma ferramenta que atua desde a especificação técnica até a implementação de código. &lt;/p&gt;
&lt;p&gt;Neste artigo, quero compartilhar minha experiência prática integrando o SpecKit no dia a dia para desenhar arquiteturas Cloud, escrever Terraform e automatizar fluxos, destacando o que funciona bem e os pontos de atenção.&lt;/p&gt;
&lt;h3 id=&quot;o-que-o-speckit-e-sua-import-ncia&quot;&gt;O que é o SpecKit e sua Importância&lt;/h3&gt;
&lt;p&gt;O SpecKit funciona como um assistente avançado que ajuda a planejar e codificar soluções técnicas. A grande vantagem é que ele não apenas &quot;gera código&quot;, mas segue um fluxo de engenharia estruturado que é vital para nós, Platform Engineers: &lt;strong&gt;Create the spec &amp;gt; Create a technical implementation plan &amp;gt; Break down into tasks &amp;gt; Execute implementation&lt;/strong&gt;, isso integrado a minha IDE like Vscode ao chat de agente, já o utilizei com o Copilot e com o Antigravity.&lt;/p&gt;
&lt;p&gt;Isso significa que, antes de cuspir dezenas de arquivos &lt;code&gt;.tf&lt;/code&gt;, ele gera um documento de constituição (&lt;code&gt;constitution.md&lt;/code&gt;) ou especificação técnica e um plano de ataque, permitindo que o arquiteto revise as premissas, a segurança e a modularização antes da execução.&lt;/p&gt;
&lt;h3 id=&quot;como-eu-configuro-e-utilizo-hands-on-&quot;&gt;Como eu configuro e utilizo (Hands-on)&lt;/h3&gt;
&lt;p&gt;A inicialização da ferramenta no repositório local é bem direta. Eu começo instalando o &lt;code&gt;specify&lt;/code&gt; na pasta do projeto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ uvx --from git+https://github.com/github/spec-kit.git specify init --here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(Fonte de referência dos meus estudos de setup)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A mágica do SpecKit para DevOps acontece quando injetamos &lt;strong&gt;Skills&lt;/strong&gt; específicas no agente. Dependendo do projeto, eu adiciono pacotes de habilidades voltadas para Terraform, GitHub Actions, ou skills de provedores de infraestrutura em nuvem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx skills add https://github.com/hashicorp/agent-skills --skill terraform-stacks
npx skills add https://github.com/jeffallan/claude-skills --skill terraform-engineer
npx skills add https://github.com/callstackincubator/agent-skills --skill github-actions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(Adicionando contexto técnico para o agente não alucinar com práticas ruins)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Com o ambiente preparado, eu utilizo o comando &lt;code&gt;/speckit.constitution&lt;/code&gt; para passar meu prompt detalhado e iniciar o planejamento. Eu costumo preparar um prompt em outras ferramentas como Gemini ou ChatGPT, etc.&lt;/p&gt;
&lt;h3 id=&quot;casos-de-uso-reais-e-como-o-speckit-se-saiu-&quot;&gt;Casos de Uso Reais (e como o SpecKit se saiu)&lt;/h3&gt;
&lt;p&gt;Durante minhas validações técnicas, apliquei o SpecKit em variados cenários de infraestrutura. Aqui um exemplo ficticio:&lt;/p&gt;
&lt;h4 id=&quot;migra-o-de-workloads-para-subnets-privadas-com-egress-controlado&quot;&gt;Migração de Workloads para Subnets Privadas com Egress Controlado&lt;/h4&gt;
&lt;p&gt;O objetivo era definir uma estratégia de migração de aplicações containerizadas para uma arquitetura com isolamento de rede mais robusto, mantendo compatibilidade com integrações externas.&lt;/p&gt;
&lt;p&gt;O prompt especificava requisitos como separação entre camadas públicas e privadas, roteamento de saída controlado, alta disponibilidade entre zonas de disponibilidade e impacto mínimo nas aplicações existentes.&lt;/p&gt;
&lt;p&gt;Resultado: A ferramenta gerou uma proposta arquitetural completa, incluindo diagramas de fluxo, plano de migração, dependências técnicas e validações necessárias para garantir conectividade entre componentes. Durante a evolução do projeto, ajustes de segurança e requisitos de integração puderam ser incorporados sem reestruturar a documentação original.&lt;/p&gt;
&lt;h3 id=&quot;pr-s-e-contras&quot;&gt;Prós e Contras&lt;/h3&gt;
&lt;p&gt;Com em PoCs executadas, aqui está o meu veredito sobre o uso do SpecKit no contexto de plataforma:&lt;/p&gt;
&lt;h4 id=&quot;pr-s&quot;&gt;&lt;strong&gt;Prós&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fluxo Racionalizado:&lt;/strong&gt; A quebra do problema em &quot;Especificação técnica&quot; -&amp;gt; &quot;Plano de tarefas&quot; -&amp;gt; &quot;Código&quot; evita que a IA crie arquivos caóticos. Se a especificação arquitetural estiver errada (ex: usar EKS em vez de Fargate), eu posso corrigir antes que ele gere o Terraform.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refinamento Contínuo:&lt;/strong&gt; É muito fácil pedir alterações granulares, como aplicar testes HCL nativos (&lt;code&gt;.tftest.hcl&lt;/code&gt;) ou injetar variáveis específicas de ambiente (&lt;code&gt;variables.tfvars&lt;/code&gt;) nos módulos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adoção de Padrões:&lt;/strong&gt; Usando as &lt;em&gt;Skills&lt;/em&gt; externas, a ferramenta já herda boas práticas de módulos oficiais da comunidade.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;contras&quot;&gt;&lt;strong&gt;Contras&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dependência de Prompts Extremamente Detalhados:&lt;/strong&gt; O SpecKit só é incrível se você souber exatamente o que quer. Eu precisei especificar até restrições como &quot;NÃO usar tecnologia X ou Y&quot; e &quot;manter tasks stateless&quot; para garantir que a IA não tomasse decisões que ferissem os princípios de custos e ou anti-patterns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Revisão Frequente:&lt;/strong&gt; Arquiteturas baseadas em nuvem envolvem políticas sensíveis de IAM e rede (ex: &lt;code&gt;s3:PutObject&lt;/code&gt; ou &lt;em&gt;peering&lt;/em&gt; de VPCs). O código gerado exige um olhar crítico com ferramentas como Checkov ou Trivy para evitar &lt;em&gt;misconfigurations&lt;/em&gt; geradas por IA.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclus-o&quot;&gt;Conclusão&lt;/h3&gt;
&lt;p&gt;O SpecKit se mostrou um copiloto poderoso para Engenharia de Plataforma. Ele não substitui o arquiteto de soluções — pelo contrário, ele &lt;strong&gt;exige&lt;/strong&gt; que o arquiteto faça as perguntas certas e defina os &lt;em&gt;guardrails&lt;/em&gt; corretos. Contudo, ao reduzir o tempo gasto na escrita de boilerplate (módulos Terraform, pipelines do GitHub Actions, Task Definitions do ECS), ele libera a equipe de plataforma para focar em segurança, governança e experiência do desenvolvedor.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21410/minha-experiencia-com-o-uso-do-speckit-no-desenvolvimento-em-infra-cloud</guid>
<pubDate>Fri, 03 Jul 2026 03:01:00 +0000</pubDate>
</item>
<item>
<title>Modern developer experience has deep roots</title>
<link>https://coderlegion.com/17562/modern-developer-experience-has-deep-roots</link>
<description>&lt;p&gt;In his 1956 account of the SAGE program, Herbert Benington highlighted the opportunity to use computers to reduce the cost of programming, documentation, and testing.&lt;/p&gt;
&lt;p&gt;The creation of utilities, compilers, and instrumentation accounted for about half of the programming effort for SAGE. Benington had recognized that writing programs to improve developer productivity was an essential investment.&lt;/p&gt;
&lt;p&gt;At the time, computers were costly, so the idea of making programmers more productive had far less economic weight than it does today. Programmers earned approximately $15,000 per year, and computers cost $500 per hour to operate. Now programmers cost about 70 times the cost of the computers they use. That means the value of developer productivity is worth far more today than it was in the 1950s.&lt;/p&gt;
&lt;h3 id=&quot;betting-on-software&quot;&gt;Betting on software&lt;/h3&gt;
&lt;p&gt;There has been a thread of developer experience all the way back to the earliest attempts to build software at scale. Savvy organizations know that money spent on providing the right environment and tools is worth more than simply “time saved”.&lt;/p&gt;
&lt;p&gt;There are two ways to look at software development: Cost and value. It certainly costs money to build software, so the software must provide value that exceeds this cost to be viable. Software systems are built based on the anticipation of value and survive if they manage to meet or exceed that expectation. When an organization becomes cost-obsessed with software, it suggests a low anticipated value or a realization that the value won’t be realized. It’s better to bravely abandon attempts with such a thin payoff.&lt;/p&gt;
&lt;p&gt;The sweet spot for software is where the value is highly likely to be obtained, or where there’s a chance of it providing a huge return on investment, so that in any 10 attempts to create value with software, a single success would pay for all the attempts and return a profit.&lt;/p&gt;
&lt;p&gt;When you consider how software is a bet, it divides software delivery approaches into two categories: cost-focused or value-focused. Traditional project management works to keep the promise of cost and timeline. Modern agile methods try to increase the probability of the bet succeeding by adjusting course as you learn more about the problem you’re trying to solve.&lt;/p&gt;
&lt;p&gt;And here’s the crucial insight. When you manage costs effectively, the best you can achieve is zero cost. When you seek out value, there is no absolute limit to how much value you could produce. It could be twice the cost, ten times the cost, or 1,000 times the cost. There is far more upside than downside if you’re creating valuable software.&lt;/p&gt;
&lt;p&gt;Cost-first approaches to software delivery decrease the probability of success, and one (of many) reasons is that it damages developer experience.&lt;/p&gt;
&lt;h3 id=&quot;devex-is-economics-not-hugs&quot;&gt;DevEx is economics, not hugs&lt;/h3&gt;
&lt;p&gt;For whatever reason, the universe decided that you must treat people well, whether you like them or not. Even when you examine organizational culture through the lens of cold, hard business goals, you’ll find that unhealthy cultures are less successful than healthy ones. You can be a philanthropist or a capitalist; either way, you have to treat your employees well, or it will damage the thing you care about.&lt;/p&gt;
&lt;p&gt;Here’s a simple way it plays out.&lt;/p&gt;
&lt;p&gt;The developers need a bit more screen real estate so they can display more information in front of them without having to switch between background and foreground apps. Additional monitors incur costs, and a cost-focused organization will likely deny the request. Developers will have a lower fully loaded cost, but produce less value. A value-focused organization sees the potential returns and their developers will get more screen space, be less frustrated, produce better work in a shorter time, and produce a lot of value.&lt;/p&gt;
&lt;p&gt;Having an extra monitor moves the needle a little, but it’s a strong signal. Once an organization chooses between the cost or value pathways, it tends to stick to that decision. That means it’s not just monitors; it’s also chairs, code editors, refactoring tools, test tools, and automation tools. The experience diverges further with each decision made based on cost rather than value.&lt;/p&gt;
&lt;p&gt;Another individual who understood the concept of developer experience was Joel Spolsky. He created &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/&quot;&gt;the Joel Test&lt;/a&gt; as a “highly irresponsible, sloppy test to rate the quality of a software team.” The Joel Test has items like “Do programmers have quiet working conditions?” and “Do you use the best tools money can buy?”&lt;/p&gt;
&lt;p&gt;I haven’t met Joel, so I can’t speak for his motivation, but I don’t need to know if he was motivated by kindness or cash. The result was an excellent workplace for developers and phenomenal value creation; a win-win, as Stephen Covey called it. Spolsky’s most famous products, Trello and Stack Overflow, sold for $425 million and $1.8 billion, respectively.&lt;/p&gt;
&lt;h3 id=&quot;you-don-t-need-to-make-it-easy&quot;&gt;You don’t need to make it easy&lt;/h3&gt;
&lt;p&gt;There’s a certain amount of inherent complexity to writing great software. You must fully grasp a problem, have a strong opinion about how to solve it, and be able to execute on your plans to make it happen. Developers don’t need protection from the difficulty of building software; they need minimal unnecessary complexity from tools, processes, and the workplace environment.&lt;/p&gt;
&lt;p&gt;There was a trend that prioritized developer comfort above all other needs, which meant providing them with frameworks to tame complexity. The frameworks made development easier, but limited a developer’s options to the extent it damaged user experience. User needs were subverted to developer ease, which is wrong and somewhat patronizing to developers.&lt;/p&gt;
&lt;p&gt;It’s not developer experience if you’re using frameworks that improve the ease of development while annoying those trying to use the software. Developer experience means providing the right environment and tools for developers to build valuable software. Software that doesn’t surprise people with a new paper cut every 5 minutes, pushing them ever closer to demanding an alternative solution.&lt;/p&gt;
&lt;p&gt;Think instead of how we set up a surgeon for success. A sterile room, excellent lighting, high-quality equipment, and working with skilled individuals who can anticipate and respond as the situation unfolds. Surgeon experience is centered around a shared goal of achieving optimal patient outcomes. We don’t simplify the scenario by removing things like the need to prevent infection; we make it possible to handle it well.&lt;/p&gt;
&lt;p&gt;Developer experience is the same. We don’t choose easier problems to solve; we set developers up to succeed at solving hard problems.&lt;/p&gt;
&lt;h3 id=&quot;modern-devex-and-platform-engineering&quot;&gt;Modern DevEx and Platform Engineering&lt;/h3&gt;
&lt;p&gt;With the rise of Platform Engineering, developer experience has been largely absorbed. Your organization might have a DevEx team, a platform team, or even both. Across the industry, the two teams share more commonalities than differences. From a list of 30 features offered by platform and DevEx teams compiled by DX, only 4 were exclusive to a single discipline.&lt;/p&gt;
&lt;p&gt;Platform only:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Certificate management&lt;/li&gt;
&lt;li&gt;DNS&lt;/li&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DevEx only:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developer training and education&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything else is along the scale from DevEx to Platform Engineering, where it may be more common in one or the other, but can be found in both.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3058189729100887388&quot; alt=&quot;Comparing DevEx and platform teams&quot;&gt;&lt;/p&gt;
&lt;p&gt;Platform Engineering and developer experience both build on Benington’s early thoughts and Spolsky’s belief that if we provide developers with the right environment and the best tools, we can amplify their skills and generate lots of value. Forming teams around this idea helps standardize and scale the approach, rather than each team being subjected to differing views based on management styles or simply not knowing what they don’t know.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://newsletter.getdx.com/p/devprod-headcount-benchmarks-q1-2026&quot;&gt;Q1 2026 DevProd headcount benchmarking report&lt;/a&gt; from DX highlights how well this scaling works. Rather than costing a fixed percentage of your engineering organization’s headcount, developer productivity teams scale non-linearly, with their ratio shrinking as the number of engineers increases. This makes sense, as their work is being reused, unlike approaches that work within individual teams and depend on teams having access to the necessary skills and knowledge.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Engineers &lt;/th&gt;&lt;th&gt;Productivity headcount&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;200-600 &lt;/td&gt;&lt;td&gt;5.1%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;600-1000 &lt;/td&gt;&lt;td&gt;4.2%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1000+ &lt;/td&gt;&lt;td&gt;3.49%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That’s not to say the goal is to have the smallest possible teams. The goal is to unlock the value you create by developing software. If having half of all engineers in productivity roles resulted in the highest levels of value creation, that would be the right mix. There is likely a point of diminishing returns if you approach 10-15%, but you should be testing this by tracking meaningful outcomes for your organization.&lt;/p&gt;
&lt;p&gt;Make sure developers have the right environment and the best tools so they can generate the most value for your organization.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17562/modern-developer-experience-has-deep-roots</guid>
<pubDate>Tue, 12 May 2026 11:37:21 +0000</pubDate>
</item>
<item>
<title>Stop Treating Angular as a Second-Class Framework for UI Components</title>
<link>https://coderlegion.com/14891/stop-treating-angular-as-a-second-class-framework-for-ui-components</link>
<description>&lt;p&gt;You’ve been promised “Angular support” more times than you can count. &lt;br&gt;
What you got instead was a React library in an Angular costume.&lt;/p&gt;
&lt;p&gt;A thin compatibility layer. A demo that technically works, but feels wrong the second you try to build something serious with it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Angular support is not the same as Angular respect.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And the more you look at how Angular tooling is actually built, the less patience you have for ecosystems that treat Angular like an afterthought.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-lie-behind-angular-support-&quot;&gt;The Lie Behind “Angular Support”&lt;/h4&gt;
&lt;p&gt;You’ve seen it.&lt;/p&gt;
&lt;p&gt;A UI library announces Angular support. &lt;br&gt;
You click the docs. &lt;br&gt;
You get excited for about 15 seconds.&lt;/p&gt;
&lt;p&gt;Then you realize what “support” really means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;awkward APIs that feel imported from another ecosystem,   &lt;/li&gt;
&lt;li&gt;missing Angular ergonomics,   &lt;/li&gt;
&lt;li&gt;strange lifecycle edges,   &lt;/li&gt;
&lt;li&gt;styling and composition patterns that clearly weren’t designed for Angular first.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Technically, yes, it works.&lt;/p&gt;
&lt;p&gt;But if your first reaction is &lt;strong&gt;“why does this feel like React with extra steps?”&lt;/strong&gt; , then it’s not a native Angular experience.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;”A wrapper is not a first-class developer experience.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And Angular developers know the difference immediately.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*-lszBtjo5UVMrDvihNB9cA.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;react-gets-products-angular-gets-adapters&quot;&gt;React Gets Products. Angular Gets Adapters&lt;/h4&gt;
&lt;p&gt;This is the part nobody wants to say too loudly.&lt;/p&gt;
&lt;p&gt;In a lot of modern tooling ecosystems, React gets the real product. &lt;br&gt;
Angular gets the adaptation layer.&lt;/p&gt;
&lt;p&gt;React gets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the first-class API,   &lt;/li&gt;
&lt;li&gt;the best examples,   &lt;/li&gt;
&lt;li&gt;the cleanest DX,   &lt;/li&gt;
&lt;li&gt;the product decisions designed around its mental model.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Angular gets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“integration”,   &lt;/li&gt;
&lt;li&gt;“compatibility”,   &lt;/li&gt;
&lt;li&gt;“support coming soon”,   &lt;/li&gt;
&lt;li&gt;or a wrapper that exposes just enough to close the sales objection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That doesn’t mean every React-first library is bad. &lt;br&gt;
It means Angular is too often treated as a market checkbox instead of a platform that deserves design respect.&lt;/p&gt;
&lt;p&gt;And once you’ve felt that enough times, you stop reading “Angular support” as a positive signal.&lt;/p&gt;
&lt;p&gt;You start reading it as a warning label.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most multi-framework libraries are really one framework with export options.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-problem-isn-t-just-pride-it-s-product-quality&quot;&gt;The Problem Isn’t Just Pride. It’s Product Quality&lt;/h4&gt;
&lt;p&gt;This is not framework tribalism for its own sake.&lt;/p&gt;
&lt;p&gt;Wrapper-heavy ecosystems create real problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;worse developer experience,   &lt;/li&gt;
&lt;li&gt;inconsistent abstractions,   &lt;/li&gt;
&lt;li&gt;harder debugging,   &lt;/li&gt;
&lt;li&gt;awkward state integration,   &lt;/li&gt;
&lt;li&gt;weaker performance characteristics,   &lt;/li&gt;
&lt;li&gt;APIs that don’t fit Angular’s strengths.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a library is born in one ecosystem and then “ported” into Angular without fully embracing Angular’s patterns, you pay for it everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in the template layer,   &lt;/li&gt;
&lt;li&gt;in reactivity,   &lt;/li&gt;
&lt;li&gt;in change detection assumptions,   &lt;/li&gt;
&lt;li&gt;in styling APIs,   &lt;/li&gt;
&lt;li&gt;in the mental overhead of using something that never really feels native.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That cost compounds fast in real products.&lt;/p&gt;
&lt;p&gt;A bad wrapper is not just ugly. &lt;br&gt;
It becomes &lt;strong&gt;architecture debt&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The ecosystem quality you tolerate today becomes the architecture debt you inherit tomorrow.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;angular-developers-are-not-asking-for-special-treatment&quot;&gt;Angular Developers Are Not Asking for Special Treatment&lt;/h4&gt;
&lt;p&gt;The ask is much simpler:&lt;/p&gt;
&lt;p&gt;If you say your library supports Angular, then build for Angular like it matters.&lt;/p&gt;
&lt;p&gt;That means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APIs that feel natural in Angular,   &lt;/li&gt;
&lt;li&gt;composition patterns that respect Angular architecture,   &lt;/li&gt;
&lt;li&gt;state handling that fits Angular’s reactive model,   &lt;/li&gt;
&lt;li&gt;docs written for Angular devs, not translated from another framework,   &lt;/li&gt;
&lt;li&gt;examples that look production-ready, not “we made this demo work once.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Angular is not some fringe hobby framework. &lt;br&gt;
It powers serious enterprise apps, internal tools, fintech platforms, compliance systems, and big teams that care about long-term maintainability.&lt;/p&gt;
&lt;p&gt;So the expectation is not unreasonable.&lt;/p&gt;
&lt;p&gt;If the framework is good enough to power critical systems, it’s good enough to deserve &lt;strong&gt;native tools&lt;/strong&gt; , not compatibility leftovers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If Angular is good enough for core systems, it’s good enough to deserve first-class tools.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-developers-are-spending-months-on-angular-first-tools&quot;&gt;Why Developers Are Spending Months on Angular-First Tools&lt;/h4&gt;
&lt;p&gt;At some point, complaining stops being interesting.&lt;/p&gt;
&lt;p&gt;You either keep using wrappers and live with the friction, or someone builds the thing they wish existed.&lt;/p&gt;
&lt;p&gt;That’s why you increasingly see stories of developers who spend months building &lt;strong&gt;Angular-first tools&lt;/strong&gt;  — including rich text editors — instead of gluing React-centric libraries into their apps.&lt;/p&gt;
&lt;p&gt;Not because the world desperately needed yet another editor. &lt;br&gt;
And not because anyone wanted a vanity side project.&lt;/p&gt;
&lt;p&gt;They did it because they were tired of pretending Angular developers should be grateful for second-hand abstractions.&lt;/p&gt;
&lt;p&gt;They wanted things that felt native from the inside out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;designed around Angular patterns,   &lt;/li&gt;
&lt;li&gt;built with &lt;strong&gt;Signals&lt;/strong&gt; in mind,   &lt;/li&gt;
&lt;li&gt;not fighting Angular templates,   &lt;/li&gt;
&lt;li&gt;not leaking another framework’s assumptions into every integration point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;If your “Angular integration” feels like React with extra steps, it’s not Angular-native.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The deeper you look at these case studies, the clearer the pattern becomes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most “Angular ports” aren’t really ports. They’re translations with compromises.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because once someone tries to build something properly for Angular, it becomes obvious how many “multi-framework” tools are really just one framework with export options.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*2kLahpvJvDYxgm12dZMmJw.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;signals-changed-the-standard&quot;&gt;Signals Changed the Standard&lt;/h4&gt;
&lt;p&gt;Angular’s ecosystem has a chance right now to stop copying old patterns and start building better ones.&lt;/p&gt;
&lt;p&gt;Signals matter here.&lt;/p&gt;
&lt;p&gt;Because an Angular-native future should not be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;old wrapper patterns with new branding,   &lt;/li&gt;
&lt;li&gt;React mental models translated into Angular syntax,   &lt;/li&gt;
&lt;li&gt;or generic abstractions that ignore what Angular is actually good at.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It should be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;signal-first APIs,   &lt;/li&gt;
&lt;li&gt;cleaner state flows,   &lt;/li&gt;
&lt;li&gt;better template ergonomics,   &lt;/li&gt;
&lt;li&gt;components designed for Angular’s architecture instead of awkwardly tolerated by it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where Angular devs need to get more ambitious.&lt;/p&gt;
&lt;p&gt;Not “Can we get this React thing working in Angular?” &lt;br&gt;
But:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What would the best possible Angular-native version of this tool look like?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s a much better question. &lt;br&gt;
And honestly, it’s long overdue.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-angular-community-needs-more-conviction&quot;&gt;The Angular Community Needs More Conviction&lt;/h4&gt;
&lt;p&gt;One of Angular’s biggest ecosystem problems is not technical.&lt;/p&gt;
&lt;p&gt;It’s psychological.&lt;/p&gt;
&lt;p&gt;Too many Angular developers have been trained to accept less:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fewer native libraries,   &lt;/li&gt;
&lt;li&gt;slower ecosystem momentum,   &lt;/li&gt;
&lt;li&gt;weaker mindshare,   &lt;/li&gt;
&lt;li&gt;and “good enough” wrapper support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That creates a strange defeatism.&lt;/p&gt;
&lt;p&gt;People start talking like Angular is lucky just to be included.&lt;/p&gt;
&lt;p&gt;It isn’t.&lt;/p&gt;
&lt;p&gt;Angular deserves builders who act like it’s worth building for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Angular doesn’t need more compatibility. It needs conviction.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That means more developers creating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Angular-first component systems,   &lt;/li&gt;
&lt;li&gt;Angular-native editors, tables, charts, and design tools,   &lt;/li&gt;
&lt;li&gt;signal-based primitives,   &lt;/li&gt;
&lt;li&gt;and products that don’t apologize for not being React.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because if Angular developers keep waiting for React ecosystems to save them, they will keep getting what they’ve always gotten:&lt;/p&gt;
&lt;p&gt;Adapters. &lt;br&gt;
Wrappers. &lt;br&gt;
Second-class support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you keep acting like Angular is second-class, don’t be surprised when the ecosystem treats it that way.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;this-isn-t-about-beating-react&quot;&gt;This Isn’t About Beating React&lt;/h4&gt;
&lt;p&gt;React is not the villain here.&lt;/p&gt;
&lt;p&gt;React won the mindshare war for a reason:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;huge ecosystem momentum,   &lt;/li&gt;
&lt;li&gt;lower barrier to experimentation,   &lt;/li&gt;
&lt;li&gt;enormous commercial gravity around component libraries and UI tooling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fine.&lt;/p&gt;
&lt;p&gt;But Angular developers keep making the mistake of treating that as proof that Angular should just consume whatever React produces.&lt;/p&gt;
&lt;p&gt;That’s not strategy. &lt;br&gt;
That’s dependency.&lt;/p&gt;
&lt;p&gt;The real question is not:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;How do we get more React libraries into Angular?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The real question is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What tools should exist because Angular exists?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s how you build a healthier ecosystem.&lt;/p&gt;
&lt;p&gt;Not by begging for parity through wrappers, but by creating products that are unmistakably native.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;native-angular-tools-will-always-feel-better&quot;&gt;Native Angular Tools Will Always Feel Better&lt;/h4&gt;
&lt;p&gt;A real Angular-native tool gives you things wrappers rarely do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APIs that match Angular instincts,   &lt;/li&gt;
&lt;li&gt;predictable integration into Angular architecture,   &lt;/li&gt;
&lt;li&gt;cleaner mental models,   &lt;/li&gt;
&lt;li&gt;fewer translation layers,   &lt;/li&gt;
&lt;li&gt;less framework dissonance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And when the product itself is designed for Angular from day one, the quality difference shows up everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in how fast teams onboard,   &lt;/li&gt;
&lt;li&gt;in how easy it is to debug,   &lt;/li&gt;
&lt;li&gt;in how natural it feels to extend,   &lt;/li&gt;
&lt;li&gt;in how much trust there is in the tool long-term.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That trust matters.&lt;/p&gt;
&lt;p&gt;Because teams don’t just adopt components. &lt;br&gt;
They adopt &lt;strong&gt;maintenance futures&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A wrapper always feels like borrowed infrastructure.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And borrowed infrastructure is the first thing people want to rip out when things go wrong.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*9mHCiSRwtBf0BYEudGmmfQ.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-manifesto&quot;&gt;The Manifesto&lt;/h4&gt;
&lt;p&gt;So here’s the position, clearly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stop calling wrappers “great Angular support.”   &lt;/li&gt;
&lt;li&gt;Stop rewarding ecosystems that treat Angular as a checkbox.   &lt;/li&gt;
&lt;li&gt;Stop acting grateful for tools that were never truly designed for Angular developers.   &lt;/li&gt;
&lt;li&gt;Stop lowering the bar for what “Angular-native” should mean.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start building:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;better Angular-first libraries,   &lt;/li&gt;
&lt;li&gt;signal-based primitives,   &lt;/li&gt;
&lt;li&gt;tooling that respects Angular architecture,   &lt;/li&gt;
&lt;li&gt;products that feel like they belong in Angular, not merely tolerate it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;If Angular is good enough for the hardest problems, it’s good enough to deserve first-class tools.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are building for Angular, build with standards high enough that nobody has to ask whether it was native.&lt;/p&gt;
&lt;p&gt;They should feel it instantly.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;angular-deserves-better&quot;&gt;Angular Deserves Better&lt;/h4&gt;
&lt;p&gt;This is bigger than one editor, one library, or one complaint.&lt;/p&gt;
&lt;p&gt;It’s about whether Angular developers keep accepting second-class tooling in a first-class framework.&lt;/p&gt;
&lt;p&gt;You can read plenty of stories of teams who inherited Angular apps full of awkward wrapper libraries and half-baked integrations. &lt;br&gt;
Once those abstractions land in a codebase, they stay there for years.&lt;/p&gt;
&lt;p&gt;That’s why this matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The shortcuts you accept at the library level become the nightmares you debug at the product level.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The ecosystem quality tolerated today becomes the architecture debt inherited tomorrow.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Angular deserves better than wrappers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And if the ecosystem is not going to hand Angular better native tools, then Angular developers will have to build them.&lt;/p&gt;
&lt;p&gt;That’s not just a technical decision.&lt;/p&gt;
&lt;p&gt;It’s a standards decision. &lt;br&gt;
A community decision. &lt;br&gt;
A respect decision.&lt;/p&gt;
&lt;p&gt;So yes — stop treating Angular as a second-class framework for UI components.&lt;/p&gt;
&lt;p&gt;And start building like it isn’t one.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;&lt;p&gt;I fix &lt;strong&gt;the Angular apps that generalists break.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m Karol Modelski, senior Angular developer and frontend architect rescuing legacy B2B SaaS frontends.&lt;/p&gt;
&lt;p&gt;If your Angular app is slowing your team down, start with a 3‑minute teardown of your current setup: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.karol-modelski.scale-sail.io/&quot;&gt;https://www.karol-modelski.scale-sail.io/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14891/stop-treating-angular-as-a-second-class-framework-for-ui-components</guid>
<pubDate>Thu, 16 Apr 2026 08:00:00 +0000</pubDate>
</item>
<item>
<title>Developer experience metrics</title>
<link>https://coderlegion.com/7015/developer-experience-metrics</link>
<description>&lt;p&gt;Developer experience (DevEx) is a key focus of &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/devops/platform-engineering/&quot;&gt;platform engineering&lt;/a&gt;. It's also a crucial success factor for any organization developing software.&lt;/p&gt;
&lt;p&gt;A recent paper by Abi Noda, Margaret-Anne Storey, Nicole Forsgren, and Michaela Freiler called &lt;a rel=&quot;nofollow&quot; href=&quot;https://dl.acm.org/doi/10.1145/3595878&quot;&gt;DevEx: What actually drives productivity&lt;/a&gt; provides a framework for measuring and improving DevEx.&lt;/p&gt;
&lt;h3 id=&quot;developer-experience&quot;&gt;Developer experience&lt;/h3&gt;
&lt;p&gt;Developers tend to be happier when they can be productive. When a developer experiences friction, it’s a sign that they can’t deliver as much value as they feel they could.&lt;/p&gt;
&lt;p&gt;The following can cause poor developer experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Working on low-value tasks&lt;/li&gt;
&lt;li&gt;Interruptions&lt;/li&gt;
&lt;li&gt;Poor tools slowing them down&lt;/li&gt;
&lt;li&gt;Unrealistic deadlines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s hard to calculate the volume of an irregular object. Archimedes discovered that submerging an object in water provides a simple way to find its volume. Similarly, improving how developers feel and think about their work and its value can increase productivity.&lt;/p&gt;
&lt;p&gt;Some benefits of improving developer experience are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Increased productivity&lt;/li&gt;
&lt;li&gt;Higher satisfaction&lt;/li&gt;
&lt;li&gt;More engagement&lt;/li&gt;
&lt;li&gt;Higher employee retention&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;core-dimensions&quot;&gt;Core dimensions&lt;/h3&gt;
&lt;p&gt;The 3 core dimensions categorize the friction developers experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Feedback loops&lt;/li&gt;
&lt;li&gt;Cognitive load&lt;/li&gt;
&lt;li&gt;Flow state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These dimensions are a valuable model for improvement.&lt;/p&gt;
&lt;h3 id=&quot;-feedback-loops&quot;&gt; Feedback loops&lt;/h3&gt;
&lt;p&gt;Software delivery involves many feedback loops:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build and test times&lt;/li&gt;
&lt;li&gt;Code review waits&lt;/li&gt;
&lt;li&gt;Manual testing&lt;/li&gt;
&lt;li&gt;Real user feedback&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these loops needs to be short to reduce friction. Ideally, all feedback loops should complete while the task is still active. If a task pauses during a feedback loop, it will interrupt the following work when it restarts, disrupting flow state and increasing cognitive load.&lt;/p&gt;
&lt;p&gt;Organizations are twice as likely to exceed performance goals with short lead times and frequent deployments. Work in smaller batches to get shorter feedback loops.&lt;/p&gt;
&lt;h3 id=&quot;cognitive-load&quot;&gt;Cognitive load&lt;/h3&gt;
&lt;p&gt;Writing and maintaining software involves a large amount of mental processing. This natural cognitive load for the task increases when developers have too many tools and technologies. The architecture of the software can also add to the burden.&lt;/p&gt;
&lt;p&gt;You can reduce cognitive load if you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduce toolchain friction&lt;/li&gt;
&lt;li&gt;Keep documentation updated&lt;/li&gt;
&lt;li&gt;Improve the system’s architecture&lt;/li&gt;
&lt;li&gt;Remove delays in the process&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/devops/platform-engineering/&quot;&gt;Platform engineering&lt;/a&gt; is a is a strategy for reducing cognitive overload caused by tools and technologies.&lt;/p&gt;
&lt;h3 id=&quot;flow-state&quot;&gt;Flow state&lt;/h3&gt;
&lt;p&gt;You can probably remember when you last felt in the flow. You may have had a good chunk of uninterrupted time and could focus on the problem. The code seemed to write itself and you made great progress. You enjoyed the work.&lt;/p&gt;
&lt;p&gt;Psychologist Mihaly Csikszentmihalyi studied the concept. He described it as a fully immersed feeling of energized focus.&lt;/p&gt;
&lt;p&gt;Flow happens naturally when you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Control over your work structure&lt;/li&gt;
&lt;li&gt;Clear goals&lt;/li&gt;
&lt;li&gt;Engaging work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s not a case of making flow happen but preventing disruption from interruptions and delays.&lt;/p&gt;
&lt;p&gt;Studies show developers who enjoy their work perform better and produce higher-quality products.&lt;/p&gt;
&lt;p&gt;You can make flow more likely by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using asynchronous communication to avoid interruptions&lt;/li&gt;
&lt;li&gt;Clustering meetings to create large blocks of meeting-free time&lt;/li&gt;
&lt;li&gt;Avoiding unplanned work&lt;/li&gt;
&lt;li&gt;Batching questions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;devex-metrics&quot;&gt;DevEx metrics&lt;/h3&gt;
&lt;p&gt;You can create DevEx metrics by measuring 3 core dimensions with 2 methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Perceptions&lt;/strong&gt; — Gathered through surveys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflows&lt;/strong&gt; — Gathered from systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many people question the value of perceptual measurements. They think they’re too subjective to be useful. However, as our &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/whitepapers/lv-measuring-continuous-delivery-and-devops&quot;&gt;white paper on metrics&lt;/a&gt; explains, they’re a vital reality check.&lt;/p&gt;
&lt;p&gt;The whole point of DevEx metrics is to uncover ways to increase the 3 core dimensions. They’re not a measure of developers but for the benefit of developers.&lt;/p&gt;
&lt;p&gt;The matrix below shows examples of the metrics you can collect. It’s intended to be a framework, so you can pick and change metrics as needed.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Dimension      &lt;/th&gt;&lt;th&gt;Perceptions &lt;/th&gt;&lt;th&gt;Workflows&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Feedback loops &lt;/td&gt;&lt;td&gt;How satisfied are you with (build times, test run speed, the time it takes to get a change live) &lt;/td&gt;&lt;td&gt;Build times, code review waits, deployment frequency, lead time for changes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cognitive load &lt;/td&gt;&lt;td&gt;How complex is (the codebase, debugging the system, understanding the documentation) &lt;/td&gt;&lt;td&gt;The number of manual stages in the deployment pipeline, lead time for technical questions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Flow state     &lt;/td&gt;&lt;td&gt;Are you able to (focus on work, avoid interruptions, understand your tasks) &lt;/td&gt;&lt;td&gt;Meeting free blocks, incident frequency, number of unplanned tasks&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you’re already collecting the &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/devops/metrics/dora-metrics/&quot;&gt;DORA metrics&lt;/a&gt;, they’re helpful here.&lt;/p&gt;
&lt;h3 id=&quot;devex-key-performance-indicators&quot;&gt;DevEx key performance indicators&lt;/h3&gt;
&lt;p&gt;You must pick key performance indicators (KPIs) to track success. You can base these on the metrics you selected as part of the dimension and method matrix.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perceived ease of delivering software&lt;/li&gt;
&lt;li&gt;Perceived productivity&lt;/li&gt;
&lt;li&gt;Employee satisfaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The KPIs make sure you remember the purpose of measuring developer experience. They also help balance the trade-offs in the improvement process. You should measure the outcomes you want from your efforts.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Developer productivity measurements are traditionally elusive. However, you can use perceptual and workflow measurements to track and improve feedback loops, cognitive load, and flow state.&lt;/p&gt;
&lt;p&gt;Using KPIs to improve these 3 dimensions will get you happier developers who help your organization achieve its goals.&lt;/p&gt;
</description>
<category>Proj Mgmt Tools</category>
<guid isPermaLink="true">https://coderlegion.com/7015/developer-experience-metrics</guid>
<pubDate>Mon, 03 Nov 2025 15:19:44 +0000</pubDate>
</item>
<item>
<title>GitOps Makes for Great DevEx, but Pragmatism Matters</title>
<link>https://coderlegion.com/4206/gitops-makes-for-great-devex-but-pragmatism-matters</link>
<description>&lt;p&gt;&lt;strong&gt;At Kubecon Paris, we surveyed expert developers about their modernization initiatives, including what works well and where there’s pain.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;KubeCon in Paris was an amazing opportunity for like-minded people to gather together to talk about Kubernetes, GitOps and cloud native computing. We took the opportunity to survey expert developers about their modernization initiatives, including what works well and where there’s pain.&lt;/p&gt;
&lt;p&gt;Developer experience (DevEx) is the primary driver for modernization initiatives. More than two-thirds of organizations are moving to cloud native, Kubernetes and GitOps to improve DevEx. Despite these good intentions, there are two common trip hazards that must be addressed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers want pragmatic GitOps, not pure GitOps.&lt;/li&gt;
&lt;li&gt;Developers need better CD tools, not just a tool that runs custom scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;developers-want-pragmatic-gitops&quot;&gt;Developers Want Pragmatic GitOps&lt;/h3&gt;
&lt;p&gt;GitOps and Kubernetes are natural companions. Compared to virtual machines, Kubernetes has superpowers in container orchestration and dynamic scaling. The ability to automate Kubernetes is also a huge advantage, and GitOps is the natural way to do this.&lt;/p&gt;
&lt;p&gt;Pragmatic GitOps stops short of saying everything should happen in version control. For example, a dedicated CD tool will handle your Kubernetes manifest file and handle the deployment process. It will apply the correct configuration for each environment and ensure software versions progress through each environment.&lt;/p&gt;
&lt;p&gt;The problem with the pure-GitOps approach is that you end up with YAML files everywhere. Not just two or three files, but thousands. It only takes a few services and environments to generate YAML sprawl, as illustrated below. If you apply different configurations per customer or location, the problem is even worse.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=234481294472584846&quot; alt=&quot;YAML can quickly get out of control as you multiply by environments and applications&quot;&gt;&lt;/p&gt;
&lt;p&gt;We spoke to Kubernetes experts at KubeCon Paris. We found twice as many advocated for pragmatic GitOps over a pure approach. (Pragmatic GitOps was preferred by 67% of experts vs. 33% who opted for pure GitOps.) As DevOps teams apply GitOps at scale, the need for specialized tooling to handle things like configuration becomes more apparent.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16171806462874036077&quot; alt=&quot;The majority of people want a more pragmatic approach to GitOps&quot;&gt;&lt;/p&gt;
&lt;p&gt;Hammers are great at what they do, but they don’t do everything. Once you’ve experienced YAML sprawl, you’ll double-check your toolbox to see what else you have available for the job.&lt;/p&gt;
&lt;h3 id=&quot;developers-want-better-continuous-delivery&quot;&gt;Developers Want Better Continuous Delivery&lt;/h3&gt;
&lt;p&gt;We know that DevEx is the №1 driver for modernization initiatives, but developers want better continuous delivery tools.&lt;/p&gt;
&lt;p&gt;The current problem stems from the fact that too many teams are using their CI tools to run CD tasks (but &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/_steve_fenton_/ci-is-not-cd-1kc8&quot;&gt;CI is not CD&lt;/a&gt;). This means they are writing scripts they then store in strings inside of YAML files. CI tools don’t offer features for environment progression, deployment observability and configuration management for environments and tenants.&lt;/p&gt;
&lt;p&gt;While these hand-cranked scripts run inside a CI tool, they are actually more like a homegrown scripted deployment solution.&lt;/p&gt;
&lt;p&gt;You have likely encountered a shadow IT project at some point in your career, where a business user has created a critical application inside an Excel spreadsheet. These inevitably become urgent rescue projects when their creator moves on or becomes overwhelmed with what they created. Creating a CD solution inside a CI tool is basically do-it-yourself shadow CD.&lt;/p&gt;
&lt;p&gt;Almost two-thirds of developers are using a CI tool or all-in-one solution for their CD pipeline, and it’s leading to an explosion in self-managed scripts as a basis for deployments. This is one of the reasons DevEx is a problem to be solved. More than 80% of developers we surveyed told us that CD is the most impactful investment that can be made for DevEx.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5421891898954827125&quot; alt=&quot;Most people agreed that CD is the most impactful investment you can make in DevEx&quot;&gt;&lt;/p&gt;
&lt;p&gt;As organizations invest in improving DevEx and software delivery performance, an appropriate consideration of CD will be crucial.&lt;/p&gt;
&lt;h3 id=&quot;update-good-practices-for-success&quot;&gt;Update Good Practices for Success&lt;/h3&gt;
&lt;p&gt;It has long been held that modern software development is fraught with cognitive overload. When you first pick up Kubernetes, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/3825/kubernetes-predictions-were-wrong&quot;&gt;ecosystem of choices causes decision overload&lt;/a&gt;. Once you’re up and running, though, it’s YAML sprawl and DIY shadow CD that are limiting your software delivery and causing pain for developers.&lt;/p&gt;
&lt;p&gt;In the “&lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/whitepapers/a-leaders-guide-to-kubernetes-cd-at-scale&quot;&gt;Leader’s Guide to Kubernetes Adoption&lt;/a&gt;,” Nikita Dergilev and Bob Walker explain that business goals need to drive your Kubernetes adoption to avoid being deprioritized by future initiatives. There are a number of decisions to make along the way that will benefit from a collaborative standardization process. The two crucial choices you can make are with respect to DIY shadow CD and YAML sprawl.&lt;/p&gt;
&lt;p&gt;We’ve moved on from traditional infrastructure because modern cloud native solutions offer so many benefits. But unintentionally, we’ve lost finesse in the way we manage software versions. Updating our good practices to support modern software is critical if we want to solve the problems of cognitive load.&lt;/p&gt;
</description>
<category>Infrastructure as Code</category>
<guid isPermaLink="true">https://coderlegion.com/4206/gitops-makes-for-great-devex-but-pragmatism-matters</guid>
<pubDate>Mon, 21 Jul 2025 11:52:53 +0000</pubDate>
</item>
<item>
<title>The Quest Continues: Porting the Word Game With AsyncSSH</title>
<link>https://coderlegion.com/2855/the-quest-continues-porting-the-word-game-with-asyncssh</link>
<description>&lt;p&gt;Last week, we reimplemented our &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/leveling-up-llm-game-development-with-dspy-356c&quot;&gt;20-questions variant&lt;/a&gt;, and it received quite a bit of attention. So far we built a web experience, as well as a command line interface for the game. However, unlike the web version, the command line interface is strictly local. There’s no way to share the game with a fellow friend unless we walk them through the setup. Coincidentally, a former coworker recently told me about hosting applications over secure shell protocol in Golang. That makes me wonder, how easily can we replicate it in Python?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*7iiy33yB4geyLDaTTY3zRw.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A cute illustration from Copilot on the topic.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-spark-of-an-idea&quot;&gt;The Spark of an idea&lt;/h4&gt;
&lt;p&gt;The secure shell protocol (SSH) is usually what we use to log into another machine for work. Yet, there are a lot of interesting applications hosted over SSH. I remember there was one that would play back a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/gabe565/ascii-movie&quot;&gt;Star Wars movie in ASCII art&lt;/a&gt; upon connecting successfully. Late last year, my coworker told me about &lt;a rel=&quot;nofollow&quot; href=&quot;https://terminal.shop/&quot;&gt;a coffee shop selling freshly brewed coffee&lt;/a&gt; over SSH. We both found that idea intriguing.&lt;/p&gt;
&lt;p&gt;He suspected the application was &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/charmbracelet/wish&quot;&gt;made with wish&lt;/a&gt;, a Golang library. That prompted me to see if there is an equivalent for Python, but I only found &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.paramiko.org/&quot;&gt;Paramiko&lt;/a&gt; at the time. After a quick glance at the documentation, I decided it was too much work and moved on. It does seem like it is capable of achieving the goal — serving a CLI application over SSH, though.&lt;/p&gt;
&lt;p&gt;Yes, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rollingstone.com/culture/culture-features/chatgpt-hypen-em-dash-ai-writing-1235314945/&quot;&gt;em-dash is intentional&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*v3oPBEIrGmyfjgZB&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Vitaly Gariev on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Keeping in touch as usual, and he would bring up the wish project from time-to-time. Then fast-forward to last week, after I was done with the reimplementation of our little word game, I stumbled upon another library called &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/&quot;&gt;AsyncSSH&lt;/a&gt;. Like Paramiko, it can be used to create an SSH server as well. The relatively more extensive examples also seemed helpful.&lt;/p&gt;
&lt;p&gt;After some exchanges with &lt;a rel=&quot;nofollow&quot; href=&quot;https://gemini.google.com/&quot;&gt;Gemini&lt;/a&gt;, I received a generated proof-of-concept code that looked workable. After completing and publishing the article on the game, I finally had time to properly explore the topic. Read on to join the ride together in building an interactive application served over SSH.&lt;/p&gt;
&lt;h4 id=&quot;finding-the-right-tool-paramiko-vs-asyncssh&quot;&gt;Finding the Right Tool: Paramiko vs AsyncSSH&lt;/h4&gt;
&lt;p&gt;Like we briefly mentioned previously, Paramiko and AsyncSSH are libraries we can use to build SSH Server applications (they are also excellent as SSH client libraries). Though the former garnered more stars on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/paramiko/paramiko&quot;&gt;GitHub&lt;/a&gt;, the amount of information on how to build an SSH Server application is surprisingly sparse. From what I read, it also required quite extensive setup.&lt;/p&gt;
&lt;p&gt;The discovery of AsyncSSH was completely unintentional. It was buried deep in the search result when I was looking for a cohesive tutorial on Paramiko. While the design of the documentation site looked as if it was built in Python 2.0 era, the extensive collection of examples is extremely helpful. I particularly liked the simplicity offered by AsyncSSH in comparison.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*3qSBi8rTUc4s1NQF&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Agence Olloweb on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Our goal for this week is a fairly simple application sporting a simple REPL-like experience. The key of this exercise is to learn, hence efficiency isn’t a deciding factor. Therefore, the simplicity and developer friendly documentation make the library a sensible choice for our project this week.&lt;/p&gt;
&lt;h4 id=&quot;two-paths-and-a-relearning&quot;&gt;Two Paths and a Relearning&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*yCF1_agka4PSzxN_&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Ross Sneddon on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The quick proof-of-concept code I came out with the help of Gemini, by referring to &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/#simple-server-with-input&quot;&gt;the documentation&lt;/a&gt;, consists of 3 components. Firstly, we have the actual SSH Server class, subclassing asyncssh.SSHServer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncssh

class MySSHServer(asyncssh.SSHServer):
    def begin_auth(self, username):
        logger.info(&quot;logging in user:%s&quot;, username)
        return False

    def session_requested(self):
        return MyREPLSession()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This class is where we define all the authentication rules. For the purpose of this article, we are skipping all of them to maintain simplicity. Thus, we are returning False in begin_auth to indicate no authentication is needed. Do refer to &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/api.html#asyncssh.SSHServer&quot;&gt;the documentation&lt;/a&gt; for information on the topic before publishing applications in production.&lt;/p&gt;
&lt;p&gt;The MyREPLSession is a class inheriting asyncssh.SSHServerSession, which defines the actual application logic we intend to serve over the server. The skeleton is shown as below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyREPLSession(asyncssh.SSHServerSession):
    _chan = None
    _buffer = &quot;&quot;

    def connection_made(self, chan: asyncssh.SSHLineEditorChannel) -&amp;gt; None:
        self._chan = chan

    def pty_requested(
        self,
        term_type: str,
        term_size: tuple[int, int, int, int],
        term_modes: dict[int, int],
    ) -&amp;gt; bool:
        logger.info(&quot;pty received&quot;)
        return True

    def shell_requested(self) -&amp;gt; bool:
        logger.info(&quot;shell received&quot;)
        return True

    def data_received(self, data: str, datatype: int | None) -&amp;gt; None:
        # TODO The actual application
        pass

    def eof_received(self):
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.exit(0)
        return False

    def signal_received(self, signal):
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.exit(0)

    def break_received(self, signal):
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.exit(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a couple of key methods we need to implement. The first is the connection_made, in our setup we are expecting a asyncssh.SSHLineEditorChannel object. All the input and output through SSH would be handled by the channel object, hence it is crucial to keep a reference of it. Methods ending with the _received suffix are event handlers, and in our toy project, we simply close the channel when no more input is sent (upon EOF or on terminate/kill signals).&lt;/p&gt;
&lt;p&gt;We will discuss data_received event handler later.&lt;/p&gt;
&lt;p&gt;Lastly, we need to tie everything together through asyncssh.create_server. As implied by the library name, the function returns a coroutine object, and hence we would need to properly set up through AsyncIO. Feel free to revisit our previous article on &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/asyncio-task-management-a-hands-on-scheduler-project-2e54&quot;&gt;how to write an asynchronous program&lt;/a&gt;. But for this example, we are adapting the example shown in the documentation.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/asyncio-task-management-a-hands-on-scheduler-project-2e54&quot;&gt;AsyncIO Task Management: A Hands-On Scheduler Project&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncio

async def main() -&amp;gt; None:
    try:
        # Ensure ssh-host key exists before running
        await asyncssh.create_server(
            MySSHServer,
            &quot;0.0.0.0&quot;,
            8022,
            server_host_keys=[&quot;./ssh-host&quot;],
            line_editor=True
        )
        print(&quot;SSH server listening on port 8022...&quot;)
    except Exception as exc:
        print(f&quot;Error starting server: {exc}&quot;)

if __name__ == &quot; __main__&quot;:
    loop = asyncio.new_event_loop()

    loop.run_until_complete(main())
    loop.run_forever()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We previously used asyncio.run to run the main coroutine, and set up a ShutdownHandler working with an exit event object to end a program. On the other hand, this approach was likely written before asyncio.run was introduced, with the manual management of event loop being the telling sign. The run_forever() here also eliminates the hack where people put an infinite loop after asyncssh.create_server.&lt;/p&gt;
&lt;p&gt;Essentially, the code snippet creates the server, where the server behaviour is defined by the MySSHServer, and we set line_editor=True to be explicit (as we are expecting user interaction in our session). Remember to create an SSH key for the application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh-keygen -q -N &quot;&quot; -t ed25519 -f ./ssh-host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As mentioned earlier, we are building a very simple REPL-like application. Just as a refresher, REPL stands for read-evaluate-print-loop, which aptly fits our case here. We read user input, get DSPy to process it with the LLM in background, print the generated response, rinse-and-repeat. The skeleton of the flow is shown as below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def main() -&amp;gt; None:
    game_setup_and_welcome_speech()

    while True:
        match input(&quot;&amp;gt; &quot;):
            case command if command == &quot;quit&quot;:
                break
            case attempt:
                process_and_print_reply(attempt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can populate our data_received handler. This method is called whenever our user is sending input through the SSH session. Unfortunately, we cannot assume the data is always ending with a newline (user pressing enter). Sometimes, the handler may have accumulated a couple of questions and send them in one go. The function may also get called while the user hasn’t done typing. For this, we would have to handle both cases, as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyREPLSession(asyncssh.SSHServerSession):
    _buffer = &quot;&quot;

    def data_received(self, data: str, datatype: int | None) -&amp;gt; None:
        assert isinstance(self._chan, asyncssh.SSHLineEditorChannel)

        self._chan.write(f&quot;Welcome to my SSH server, {username}!\n\n&quot;)
        self._buffer += data

        lines = self._buffer.split(&quot;\n&quot;)
        for line in lines[:-1]:
            match line:
                case command if command == &quot;quit&quot;:
                    self._chan.exit(0)
                    break

                case attempt if attempt:
                    process_and_print_reply(attempt)

        self._buffer = lines[-1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember we talked about handling user input and output through the SSHLineEditingChannel object we received in connection_made? Output of our application is done through its write() method, just like how we output to stdout (to be displayed on the terminal emulator) through print() usually.&lt;/p&gt;
&lt;p&gt;Let’s test the application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*nzSnTP5-lU5fShc3HDLN3A.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A seemingly stalled game session&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While the application worked, the experience wasn’t ideal. My modest GPU took quite a bit of time to generate a response for every user input, and somehow the cursor stalled and wouldn’t move to another line whenever the user pressed enter. It somehow gave an impression of the application just died after every enter.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*5p_SWLXidmWYQ065&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Josh Redd on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I was ready to move on, and start preparing to write, until I came across &lt;a rel=&quot;nofollow&quot; href=&quot;https://asyncssh.readthedocs.io/en/latest/#line-editing&quot;&gt;another example&lt;/a&gt; that fits the use-case. Now we have a code that looked a lot like the original skeleton.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_client(process: asyncssh.SSHServerProcess) -&amp;gt; None:
    username = process.get_extra_info(&quot;username&quot;)
    process.stdout.write(f&quot;Welcome to my SSH server, {username}!\n\n&quot;)

    game_setup_and_welcome_speech()

    while True:
        match await process.stdin.readline():
            case command if command == &quot;quit&quot;:
                break

            case attempt:
                process_and_print_reply(attempt)

    process.exit(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Previously we returned output through the SSHLineEditingChannel object, here we are doing it through the SSHServerProcess object. On the other hand, instead of input(), we read it through the readline() method in the stdin (an SSHReader object) property.&lt;/p&gt;
&lt;p&gt;Then, we need to remove the session_requested method from MySSHServer, and define the process_factory argument in asyncssh.create_server to point to our new coroutine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await asyncssh.create_server(
    MySSHServer,
    &quot;0.0.0.0&quot;,
    8022,
    server_host_keys=[&quot;./ssh-host&quot;],
    line_editor=True,
    process_factory=handle_client,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Re-run the application again, and to my surprise, it worked almost exactly like the original CLI version. For our project, the second approach definitely worked better. Nonetheless, the first attempt was also crucial, as it gives an idea on how input is being sent in a SSH session.&lt;/p&gt;
&lt;p&gt;The choice of naming the property holding a SSHReader object as stdin, and the fact it reads input through a method named readline piqued my curiosity. I knew we could reach sys.stdin to read content redirected from the previous command (via the pipe | operator), or file content via the &amp;lt; operator (in Unix-like environments). That said, I didn’t know reading user input interactively in a running application is done through stdin as well.&lt;/p&gt;
&lt;p&gt;My first experience writing a production CLI application started with PHP (very strange, yes). That was my first time learning about the relationship between the operators and stdin, stdout, and stderr through my supervisor. The scripts didn’t really need to collect input interactively, and given the similarity of PHP and C, I couldn’t find a scanf equivalent so I assumed PHP doesn’t have such feature.&lt;/p&gt;
&lt;p&gt;I was wrong.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*QGYcCYtRbatdkQ4B&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Mr. Bochelly on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After some exchanges with Gemini, I realized I could use &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.php.net/manual/en/function.readline.php&quot;&gt;readline()&lt;/a&gt; in PHP for the task. Which itself is similar to scanf and input in Python. Alternatively, I could also use fgets(STDIN) to do the job, just that I need to figure out a way to print a prompt prior to the input collection. On the other hand, if I redirect some content into a Python application, if I call input(), it would read the content in stdin, until it encounters a newline character. Essentially, it roughly equates&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys

sys.stdin.readline().strip()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Back to the topic, the toy example skipped many other aspects besides the authentication work we mentioned earlier. Unfortunately, due to the sheer scope and complexity, it is almost impossible for the developer to cover all use cases in the examples. Fortunately, the API documentation is very well-written, and I assume it could be useful for people who know more about the protocol.&lt;/p&gt;
&lt;p&gt;We are lucky to live in an era where a LLM chatbot like Gemini is readily available, to provide help in navigating the documentation. Besides that, it also serves as a valuable companion to help gathering complementary information, like how I relearned stdin. Even so, critical thinking is still crucial while evaluating the response to avoid falling victim to AI hallucination.&lt;/p&gt;
&lt;h4 id=&quot;bridging-game-and-server&quot;&gt;Bridging Game and Server&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*B-C31lKKu6WXm70m&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Phil Hearing on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We finally assembled all the pieces together and ported the game to be served over SSH. The concrete implementation can be found at the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/guessme-llm/&quot;&gt;GitHub repository&lt;/a&gt; for those who are interested. Both the CLI version and SSH version (the handle_client variant) are very similar, as shown below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_client(process: asyncssh.SSHServerProcess) -&amp;gt; None:
    username = process.get_extra_info(&quot;username&quot;)
    process.stdout.write(f&quot;Welcome to my SSH server, {username}!\n\n&quot;) # vs print()

    game_setup_and_welcome_speech()
    while True:
        match await process.stdin.readline(): # vs input()
            case command if command == &quot;quit&quot;:
                break
            case command if command == &quot;end&quot; and answer is not None:
                answer = None
            case attempt:
                process_and_print_reply(attempt)

    process.exit(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The major difference between the two, is the use of process.stdout.write() to replace print(), and process.stdin.readline() to replace input (though a new coroutine called prompt_input is written to compose a process.stdout.write() to display prompt text).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def prompt_input(process: asyncssh.SSHServerProcess, prompt: str) -&amp;gt; str:
    process.stdout.write(prompt)
    return await process.stdin.readline()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The REPL-like experience makes the word game a very synchronous experience. A response is only generated upon user submission of input. Therefore the await is necessary while expecting user input is needed to ensure order. Other than that, the asynchronous nature of AsyncSSH does not affect the game’s logic much. As each game session is independent, each time a new user connects, the library will run handle_client separately for each of them.&lt;/p&gt;
&lt;p&gt;Once the log message shows “SSH server is listening …” after executing ssh.py, it is ready to be connected to with the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh -p 8022 localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Considering we didn’t define any authentication methods in MySSHServer (which is definitely a bad practice), it should display a welcome message and the game is started right away.&lt;/p&gt;
&lt;p&gt;In this article, we explored two different strategies to host our little word game over SSH. The two approaches have their own pros and cons, but it does show how AsyncSSH makes the development experience quite intuitive. Even though I do not have much experience doing this in Golang with wish (the documentation situation is also quite bad there), I enjoy the time spent doing this with AsyncSSH.&lt;/p&gt;
&lt;h4 id=&quot;the-game-goes-live&quot;&gt;The Game Goes Live&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*yEHAu6wSvV2IeEN1&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Alessandro Erbetta on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The point of the exercise is mainly on learning and discovery. If our game were developed with a text-based UI through &lt;a rel=&quot;nofollow&quot; href=&quot;https://textual.textualize.io/&quot;&gt;Textual&lt;/a&gt;, or even &lt;a rel=&quot;nofollow&quot; href=&quot;https://typer.tiangolo.com/&quot;&gt;Typer&lt;/a&gt;, some extra work is needed to ensure I/O is redirected properly.&lt;/p&gt;
&lt;p&gt;The experience of porting the library to SSH is a series of happy coincidences. It started with a spark from a discussion with my former coworker, and it led to a series of discoveries. This article is a culmination of the whole journey, sharing what I learned throughout. It turns out building an application to be served over SSH just needed a change in how I/O is done. Gemini also helped immensely throughout, deepening my understanding.&lt;/p&gt;
&lt;p&gt;We went through the whole process by configuring a server by subclassing a asyncssh.SSHServer, exploring two different ways to serve applications through process_factory and inheriting asyncssh.SSHServerSession, then getting it running via asyncssh.create_server.&lt;/p&gt;
&lt;p&gt;Hopefully this article sparks an interest and we see more interesting applications hosted over SSH.&lt;/p&gt;
&lt;p&gt;This article was written with assistance from Gemini, a large language model by Google. Gemini provided editorial help, fixing language errors and checking for article flow. However, the code and voice are entirely my own. If you’d like to collaborate or discuss job opportunities, please connect with me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://linkedin.com/in/jeffrey04/&quot;&gt;LinkedIn&lt;/a&gt; or via &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
</description>
<category>More Libraries</category>
<guid isPermaLink="true">https://coderlegion.com/2855/the-quest-continues-porting-the-word-game-with-asyncssh</guid>
<pubDate>Sun, 04 May 2025 23:33:09 +0000</pubDate>
</item>
</channel>
</rss>