# Making My Codebase Agent-Ready
URL: https://aleromano.com/posts/agent-ready-codebase
Date: 2026-06-24
A while ago I wrote about [making my site easily digestible](/posts/agent-ready) by the agents that browse the web: markdown endpoints, `llms.txt`, structured data.
This time I focused on my own Developer eXperience — letting me (or rather, letting an agent) write more code without introducing regressions. I started from a question:
> What stops a tireless, fast, plausible-sounding contributor with zero accountability from quietly breaking things?
The answer turned out to be the same thing that makes [trunk-based development](https://trunkbaseddevelopment.com/) work for humans: a scaffolding of **deterministic gates**. This is the story of building that scaffolding — and of the very real bugs that surfaced along the way.
## The Incident That Started It 🧨
It began with the most boring task imaginable: a Dependabot PR bumping `nodemailer` from 8 to 9. I asked the agent to check it out, run the tests, and verify the contact form still sent email.
It did. But while poking around it noticed two things.
First, one test was **failing** — a cache TTL assertion expecting one hour while the code said ten minutes. Someone (me) had changed the source months earlier and never updated the test.
Second, and far worse: **that failing test had never failed CI.** My pipeline only built a Docker image and deployed it. There was no `npm test` step anywhere, and no `pull_request` trigger. The tests existed. They simply never ran. A broken assertion had been sitting green for who knows how long.
So: I had tests, but I did not have **gates**. And if I could not trust my own safety net, I certainly could not hand an autonomous agent the keys.
So I set off on a little journey.
## Step 0: Make the Tests Actually Run ✅
The unglamorous foundation. I added a `checks` job to the workflow that runs the type checker and the full test suite on every push **and every pull request**, and made build-and-deploy depend on it.
Honestly, I was convinced I had wired the tests into CI since day 0 😅. I know the value of a fast, reliable battery of unit tests, so I have always written them — even for this personal project. It's just that if I didn't run them by hand, they didn't run. FIXED!
## Step 1: Coverage — Necessary, Not Sufficient 📏
Next I added a coverage gate, set up as a **ratchet**: the threshold sits a couple of points below the current number, so any change that *drops* coverage fails the build, and as coverage climbs I raise the baseline.
What does coverage actually do? **It measures execution, not verification.** A line can be 100% covered and 0% checked, if your test runs it but never asserts anything about the result. Coverage tells you the code *ran*. It cannot tell you the test was *working*.
Coverage is necessary — you cannot verify code you never execute — but treating it as a quality target is how you get [Goodhart's law](https://en.wikipedia.org/wiki/Goodhart%27s_law) and a codebase full of assertion-free tests that exist purely to make a number go up. In fact I consider it a "dangerous" metric: the moment it becomes a target, it can be gamed and turn *misleading* (I still remember "colleagues" testing getters and setters in the Java world…).
In the agentic world, though, it has its use: steering the AI toward output that is tested by design, instead of writing code that will get tests later (or, more likely, NEVER).
## Step 2: Mutation Testing — Testing the Tests 🧬
[Mutation testing](https://en.wikipedia.org/wiki/Mutation_testing) attacks your code to see whether your tests are any good. It introduces small faults into the source — flips a `>` to `>=`, turns `if (x)` into `if (true)`, deletes a line — and re-runs your suite against each mutated version (a *mutant*).
- If a test **fails** on the mutant, the mutant is "killed" — your tests would have caught that bug. Good.
- If every test still **passes**, the mutant **survived** — you have code whose breakage no test would notice. A hole, quantified.
The idea comes from a 1978 paper by DeMillo, Lipton and Sayward, and it answers the exact question coverage cannot: *"if this code were wrong, would anything go red?"*
I pointed [Stryker](https://stryker-mutator.io/) at my `utils` folder. The headline:
> One file had **75% line coverage but a 46% mutation score.** Over half of its logic could be silently broken and not a single test would complain.
It even pinpointed the kind of bug that hides behind green coverage. In `github.ts` I have a fallback that, if the GitHub API is unreachable, serves a cached copy even when it is stale. Stryker mutated that branch's condition and the mutant **survived**:
```text
[Survived] ConditionalExpression
src/utils/github.ts:209:11
- if (staleCached) {
+ if (true) {
```
My "handles API errors gracefully" test passed anyway: it checked that the endpoint returned a clean error, but not that it *wasn't* serving stale data when there was none. With `if (true)` the branch was always taken, and nobody noticed. One line of assertion was enough to kill the mutant — **without adding a single covered line**:
```ts
// in the "should handle API errors gracefully" test
expect(mockConsoleWarn).not.toHaveBeenCalledWith(expect.stringContaining('stale'));
```
That is the whole lesson in one diff: strength is not coverage.
## Step 3: Architectural Fitness Functions — Rules an Agent Cannot Break 🏛️
Up to here I was testing *behaviour*. But a lot of what keeps a codebase healthy is **structure**: the database driver stays behind its module, components do not import pages, the Italian routes reuse the shared shells. That knowledge lived in my `CLAUDE.md` as prose.
Borrowing from [*Building Evolutionary Architecture*](https://www.thoughtworks.com/en-us/insights/books/building-evolutionary-architectures), I turned those rules into **fitness functions**: plain tests that scan the codebase and fail the build on violation. A handful of them:
- Components must not import from `pages/`.
- `better-sqlite3` may only be imported inside `utils/database/`.
- Every Italian page must delegate to a `Base*` shell.
- The middleware must guard `/admin`.
The punchline for agentic development: **an agent cannot violate a rule that is mechanically enforced.** A guideline in a doc is a suggestion it might ignore. A fitness function is a fence it cannot walk through. I did not even delete the prose — I *demoted* it: the doc now explains the *why* (guidance for whoever is writing the code), and the test owns the *what* (the verdict). Document the intent; enforce the rule.
## Step 4: Performance Budgets — A Fitness Function for Speed ⚡
Same shape, different goal. The point of this site being Astro is that it ships almost no JavaScript. Nothing stopped an agent (or me) from importing some heavy library into a component and quietly undoing that.
So I added a [bundle-size budget](https://github.com/aleromano92/aleromano.com/blob/main/scripts/check-bundle-budget.mjs): a script that runs after the build and fails if the client JavaScript grows past its limit. The fiddly bit was that my biggest bundle by far is the reveal.js presentation runtime (~1.1 MB), which only loads on `/present` routes. A naive "total JS" budget would just be measuring reveal.js and would happily wave through a regression in the code that runs on every page. So I isolated it on its own budget line and put a separate, tighter ceiling on the app-wide JavaScript.
One deliberate **non-choice** here, which matters: I did *not* make Lighthouse or load tests into gates. More on why in a second.
## Step 5: Property-Based Testing & Residuality — Stressing the Whole Space 🎲
Every test so far checks **points**: for input X, expect output Y. You pick X, so you only ever check the cases you imagined. The bug lives in the case you didn't.
[Property-based testing](https://github.com/dubzzz/fast-check) inverts this. Instead of "for this input, this output," you state an **invariant that must hold for every input**, and the tool generates hundreds of random inputs trying to break it, then *shrinks* any failure to the minimal case:
- For *any* string, reading-time returns a whole number ≥ 1 and never `NaN`.
- For *any* referer, normalizing it twice equals normalizing it once.
- For *any* JSON body, the contact endpoint returns a sane status and never crashes.
This connects to [residuality theory](https://www.sciencedirect.com/science/article/pii/S1877050920305585) (Barry O'Reilly): a system is best understood by its **residues** — what survives after you hit it with **stressors**. You design for resilience by enumerating the stressors and making sure a coping residue exists for each. Random inputs, dependency failures, hostile payloads: an autonomous agent is itself a stressor generator, so the right question stops being "did I test what I imagined?" and becomes "what is the universe of stressors, and does a residue survive each?"
It paid off within minutes. A generated stressor produced:
```js
normalizeReferer("git:foo") // → "null" (the literal string!)
```
`URL.origin` returns the *string* `"null"` for non-`http(s)` schemes, and my analytics was storing that as a referer. No example test would ever have tried `git:foo`. The idempotence property found it, shrank it to four characters, and I fixed it by restricting to real http(s) referers.
## Why "Deterministic" Is the Whole Point 🎯
A pattern runs through all of this, and it is the reason I keep saying *gate* and not *test*.
A gate returns a binary, **reproducible** verdict and is allowed to block the merge. That bar is higher than "a test that runs," and determinism is non-negotiable for three reasons:
1. **A flaky gate gets disabled.** The first time a check goes red for reasons unrelated to your change, humans learn to rerun until green. A non-deterministic gate is *worse* than no gate, because it trains everyone to ignore red.
2. **It replaces slow human judgement.** This is the trunk-based development link: continuous integration is only safe because a fast, trustworthy gate stands in for the long review queue.
3. **It is the agent's feedback signal.** An agent has no taste, no fear, and no memory of the last outage. You have to externalise judgement into mechanical fences — and a fence is only useful if it is in the same place every time.
This is exactly why I refused to gate on Lighthouse or load tests. Their scores wobble on shared CI runners. They are fantastic **monitors** — noisy, observational, trend-watching — but a **gate** must be deterministic. Same data, different job. Confusing the two gives you either flaky pipelines or unguarded regressions.
And the randomized tests? I pinned the seed. Property-based testing is random in *method* but deterministic in *verdict*: a failure is always reproducible. The exploration is a feature; the flakiness is not.
## What I Actually Got 🧩
Putting all the steps together:
- Example tests check **chosen behaviour**.
- Coverage checks **whether I looked**.
- Mutation testing checks **whether my looking is sharp**.
- Architectural fitness functions check **the shape of the system**.
- Performance budgets check **its cost**.
- Property-based testing checks **the whole input space, under stress**.
Having laid these foundations, I feel far more confident about the next step: building *loops* where different signals can spin up new work on my site for an AI that then produces far more code than I could ever review in my limited time. With these gates in place, I feel safe merging — and sleeping soundly.
---
# Making My Site Agent-Ready
URL: https://aleromano.com/posts/agent-ready
Date: 2026-04-25
Here is something I already established in my [about this site presentation](/posts/about-this-site): the web has always been mostly bots. Port scanners, crawlers, feed readers, synthetic monitors. Humans are a minority in your server logs, and always have been.
What has changed is the quality of the bots. The old ones were indiscriminate: they would happily crawl anything and stuff it in an index. The new wave of AI agents is considerably more demanding. Perplexity, ChatGPT with browsing, Claude's web search, and hundreds of custom RAG pipelines now fetch web pages to build context for answering questions. And these agents, unlike their ancestors, have opinions: they would much rather receive clean markdown or plain text than dig through layers of HTML, navigation markup, and JavaScript bundles.
I came across [Dennis Morello's article on configuring his site for AI discoverability](https://morello.dev/blog/configuring-my-site-for-ai-discoverability) and one line stuck with me:
> "If your site isn't readable by those agents, you don't exist to them."
Enough to convince me spending a Saturday evening on this (it was the Festa della Liberazione, happy Freedom to everyone!).
Here are the **7 changes I made, reusable for any static or server-rendered site**.
## The Starting Point 📊
His article also pointed me to [isitagentready.com](https://isitagentready.com), a tool that scans your website and scores how well it is set up for AI agents. I ran it on aleromano.com.

25 out of 100. Level 1: Basic Web Presence. The site existed on the web, which counted for something, but not much more from an agent's perspective.
The breakdown:
- **Discoverability: 67** (2/3 checks) --> partial credit, mostly from having a sitemap
- **Content: 0** (0/1 checks) --> no machine-friendly content formats
- **Bot Access Control: 50** (1/2 checks) --> robots.txt existed but lacked AI-specific directives
- **API, Auth, MCP & Skill Discovery: 0** (0/6 checks) --> nothing
The site works fine for humans. But if an AI agent wanted to understand what this blog is about, it had to parse every HTML page, fight through navigation, scripts, and layout markup, and somehow extract the actual content. Wasteful, fragile, and often inaccurate.
I decided to fix it. Here is what I did.
## `Content-Signal` in robots.txt 🤖
The simplest change first. The `robots.txt` vocabulary has expanded to cover AI-specific preferences beyond the traditional `User-agent` and `Disallow` directives.
I added one line:
```
Content-Signal: search=yes, ai-train=no, ai-input=yes
```
This tells crawlers: index me for search, don't use me for model training, but feel free to use my content as context when answering questions. The distinction between `ai-train` and `ai-input` matters. I am happy for my posts to help someone get a useful answer from a chatbot. I would rather not have them silently absorbed into a training dataset without attribution.
The spec lives at [contentsignals.org](https://contentsignals.org/) and is still evolving, but writing it costs nothing.
## Accurate Sitemap Dates 📅
My sitemap was using the current build date as `lastmod` for every page. 🤦 Technically wrong: it told crawlers that every page was updated today, on every build.
I updated the sitemap to read the `pubDate` from each post's frontmatter and use that as `lastmod` instead. Now a post from 2022 correctly signals that it has not changed since 2022. Crawlers can decide whether to re-fetch based on accurate information.
The `@astrojs/sitemap` integration has a `serialize` hook for exactly this.
## Markdown Endpoints 📄
This is the most impactful change for AI consumers.
Every blog post now has a parallel URL ending in `.md` that serves the raw markdown source. `/posts/about-this-site` serves the full HTML page with navigation, styles, and scripts. `/posts/about-this-site.md` serves just the content, with `Content-Type: text/markdown`.
For an AI agent, parsing raw markdown is dramatically cheaper than parsing HTML. No DOM traversal, no selector guessing, no stripping nav elements and footer widgets. Just the text.
Implementing this in Astro meant creating a new endpoint file named `[...slug].md.ts`. The `.ts` extension is stripped by Astro's router, leaving `.md` as the route suffix. The handler grabs `post.body` from the content collection and returns it. Around ten lines of code.
The best part: this comes essentially for free. I already write every post in markdown. The endpoint just exposes what is already there, with no conversion, no preprocessing, no extra tooling.
Italian translations get their own `.md` endpoints too, at `/posts/it/slug.md`.
## Advertising the Markdown URLs 🔗
Having the endpoints is half the job. The other half is making sure agents can discover them easily.
I added two layers of "advertisement".
**In the HTML `
`:**
```html
```
Any agent that fetches the HTML and reads the head finds the markdown alternative immediately.
**In HTTP response headers:**
```
Link: ; rel="alternate"; type="text/markdown"
```
This is more powerful: the information is available before the HTML body is even parsed. Per [RFC 8288](https://www.rfc-editor.org/rfc/rfc8288), `Link` response headers are the right mechanism for advertising resource relationships at the HTTP layer.
I extended the existing Astro middleware (already used for /admin auth) to append `Link` headers on every response. Blog post pages get the post-specific markdown link. All pages get global links pointing to the RSS feed, sitemap, and LLM index files.

## llms.txt and llms-full.txt 📚
A [growing convention](https://llmstxt.org/) in the AI-friendly web is to publish an `llms.txt` file at your site root. Think of it as a robots.txt for AI consumers: a plain-text index that gives agents a map of your content without making them crawl every page individually.
I implemented two variants:
**`/llms.txt`** is a structured index listing every English post with its title, description, and a direct link to the `.md` endpoint:
```
# Alessandro Romano
> Alessandro Romano's thoughts.
## Blog Posts
- [Making My Site Agent-Ready](/posts/agent-ready.md): How I went from...
- [My Friend Stress-Tested My Website](/posts/friend-stress-tested-my-website.md): A story about...
```
**`/llms-full.txt`** is the complete corpus: every post's full markdown concatenated into one file, separated by dividers with a header per post. Useful for agents that want comprehensive context without making dozens of individual requests.
Italian translations are intentionally excluded from both files. They are translations of existing English content and would just be duplicate material in AI corpora.
## Agent Skills Discovery Index 🗂️
A newer and still-evolving convention: a machine-readable file at `/.well-known/agent-skills/index.json` listing what AI-relevant capabilities your site exposes. The spec is a Cloudflare draft RFC ([agentskills.io](https://agentskills.io)).
Mine lists four entries: the LLM index, the full corpus, the RSS feed, and the sitemap. It is a single static JSON file with essentially zero maintenance cost.
Early days for this spec, but publishing it is trivial.
## Structured Data (JSON-LD) 🏷️
Every blog post now includes a `BlogPosting` JSON-LD block in the ``. This gives agents structured, typed metadata: word count, reading time, publication date, the article body as plain text, author identity with their areas of expertise, and a breadcrumb trail.
The infrastructure was already in place in the `SEO` component: it accepted a `structuredData` prop and rendered it as a script tag. I just had to build the right object in `BlogPostLayout` and pass it in. The `author.knowsAbout` list reuses the same values from the `Person` schema on the about page, keeping things consistent.
## What I Deliberately Skipped ❌
The audit also flagged several things I chose not to implement.
**API Catalog (RFC 9727):** The site has no public API. The `/api/` routes are all internal (analytics, contact form, rate limiting). Publishing an API catalog for internal endpoints would be misleading.
**OAuth and protected resource metadata:** Admin access uses HTTP Basic Auth. No OAuth anywhere. Not applicable.
**MCP Server Card:** The spec is still an open draft PR. The site does not expose an MCP server, so there is nothing to advertise. Premature.
**WebMCP (`navigator.modelContext`):** WebMCP lets a page expose tools and actions to an in-page AI model. A blog has no meaningful actions to expose: there is nothing for an agent to do here besides read. Content discovery is already handled by llms.txt, so WebMCP would add nothing.
**`Accept: text/markdown` content negotiation:** Some implementations serve markdown when a request includes this header. The static `.md` URL approach covers the same use case without the complexity of content negotiation and is far easier to cache.
## Is This The Future I Like? 🤔
The web has always adapted to how content gets consumed. We added RSS for feed readers. We added Open Graph tags for social previews. We added structured data for search result snippets. AI agents are the next layer of consumer, and the investment to support them is genuinely small: a few endpoint files, a couple of response headers, two text files, and a JSON blob.
The before score was 25. Here is the after:

50 out of 100. A solid jump, and I will take it. The remaining gap is almost entirely the `Accept: text/markdown` content negotiation check, which I deliberately skipped (see above). The tool rewards serving markdown when the request includes that header; the static `.md` URL approach covers the same practical need without the complexity, but the scanner does not credit it. The other failed checks are the ones I skipped intentionally: API catalog, OAuth metadata, and MCP server card.
## Bonus: Split traffic in Grafana 📈
As a companion to these changes, I added a panel to the site's Grafana logs dashboard that splits nginx traffic into three series by user agent: known AI agents (GPTBot, ClaudeBot, PerplexityBot, and similar LLM-powered consumers), known crawlers (Googlebot, Bingbot, social preview bots), and everyone else.

This screenshot is from a local test run where I sent 50 fake AI agent requests and 20 fake human requests to verify the panel. The "humans" line is higher because it also picks up the browser traffic from loading Grafana itself. On the real production site the ratio will be more telling.
A Loki regex over user agents is not perfect detection: agents that do not self-identify land in the humans bucket, and some spoof browser strings deliberately. But the ones that do announce themselves give enough signal to be worth watching.
---
# My Friend Stress-Tested My Website Without Telling Me
URL: https://aleromano.com/posts/friend-stress-tested-my-website
Date: 2026-04-14
I was doing my usual check of my analytics dashboard when I noticed something strange.
The top page for the week was `/anatoli-was-here<3`, with **11,604 views** and a single unique visitor. A blog post I had never written. A path that didn't even exist on my site. And apparently, every single one of those 11,604 requests came from the same source: `https://aleromano.com/posts/built-in-browser-ai`.
I stared at this for a good thirty seconds before my brain connected the dots.

Then I sent a message to [Anatoli Nicolae](https://anatolinicolae.com/). One of those colleagues you genuinely go to the office just to see, the kind of person who makes a random Tuesday feel worth the commute. Sharp, funny, endlessly curious, and apparently running `k6` load tests against my website at full tilt without mentioning it first. 😅
He confirmed he wasn't trying to DDoS me, just experimenting with `k6`. He later sent me his script with the cheerful energy of someone who had just done me a massive favour. Which, honestly, he had.
## What He Did 🧪
Anatoli ran a `k6` load test that ramped from 20 to 100 virtual users over about three and a half minutes, each hitting my analytics collect endpoint with a custom path. The `/anatoli-was-here<3` marker was his way of signing his work. The analytics system is something I built myself; if you're curious how it works, I covered it in detail in [this slide of my DIY in the AI era talk](/posts/about-this-site/present#/25).
The result? Two things, actually. The good news ✅: my site handled it. The VPS didn't buckle, the response times stayed within acceptable bounds. But I had never actually *verified* any of that myself. I had deployed my site, tuned it a bit, added observability, and then just assumed it would hold up under pressure. Anatoli's unannounced stress test was the first real proof I had that it would.
The bad news ❌: the analytics endpoint accepted whatever data Anatoli threw at it, no questions asked. Any path, any payload. That's how `/anatoli-was-here<3` ended up as my most visited page of the week.
I patched this in `nginx`. The analytics collect endpoint now has its own rate-limiting zone 🔒:
```nginx
limit_req_zone $binary_remote_addr zone=ANALYTICS:10m rate=30r/m;
location = /api/analytics/collect {
limit_req zone=ANALYTICS burst=20 nodelay;
limit_req_status 429;
# ... proxy to app
}
```
The `rate=30r/m` part is the steady-state limit: at most 30 requests per minute from any single IP. That works out to one request every two seconds on average. A real person clicking around your site (opening a post, navigating to the home page, reading another article) sends maybe a handful of analytics pings per minute. 30 is generous.
But there is a problem with a hard per-second rate limit: legitimate traffic is bursty. You open a tab, your browser fires three requests almost simultaneously. Or you click through a few links quickly. If nginx enforced exactly one request every two seconds with no tolerance, those bursts would trigger false positives and drop real users.
That is what `burst=20` handles. Think of it like a token bucket: nginx gives each IP a bucket that holds 20 tokens. Every request spends one token. Tokens refill at the configured rate (30 per minute, so one every two seconds). If you fire 5 requests at once, that is fine: you spend 5 tokens and the bucket still has 15 left. If you fire 25 requests at once, the first 20 are accepted (bucket drained), and the remaining 5 get a 429. The `nodelay` flag means nginx does not queue them up and drip them through slowly; it either serves them immediately or rejects them.
So a real user navigating the site will never hit this limit. A flood script hammering the endpoint at hundreds of requests per minute will exhaust the burst in the first second and get blocked for the rest.
It does not prevent someone from crafting a slow trickle of fake paths (rate limiting is not input validation), but it does stop the kind of unthrottled hammering Anatoli's test demonstrated. The app-level origin check is a separate layer that handles the rest.
## Why Load Testing a Personal Site Still Matters 🏋️
It's tempting to think load testing is only for companies with millions of users and dedicated SRE teams. But even for a personal site on a VPS, it matters for a few concrete reasons:
- **You don't know your limits until you hit them.** A site that feels fast with 1 user might struggle badly with 50 concurrent ones. Without testing, that discovery happens in production, at the worst possible moment.
- **VPS resources are fixed.** Unlike cloud auto-scaling, a single Hetzner box has a ceiling. If you write a blog post that gets shared on Hacker News, you want to know ahead of time whether your server will survive the traffic spike.
- **Regressions happen silently.** A database query you added, a third-party call you introduced: any of these can quietly degrade performance. Regular load tests catch this before users do.
- **It gives you confidence.** There is something genuinely calming about knowing your site has been hammered with 100 concurrent connections and came out fine.
## Types of Tests You Should Know 📚
Before picking a tool and running it, it helps to understand what you are actually measuring.
**Smoke test**: the minimum viable check. A handful of virtual users for a short burst, just to confirm nothing is obviously broken. Run this after every deploy.
**Load test**: a sustained simulation of realistic traffic. You define a target number of concurrent users and hold it for long enough to reveal memory leaks, connection pool exhaustion, or response time degradation under steady pressure.
**Stress test**: you push beyond your expected ceiling to find the breaking point. The goal is not to pass; it is to discover *where* you fail and *how gracefully*.
**Soak test** (also called endurance test): you hold a moderate load for a long time (hours, sometimes days). This is the one that catches slow memory leaks and database connection drift that only appear over time.
For a personal site, smoke and load tests are the essential two. Stress testing is valuable if you are about to do something that might drive a traffic spike (a product launch, a conference talk, getting posted on a popular aggregator).
## Setting It Up with `autocannon` ⚙️
[autocannon](https://github.com/mcollina/autocannon) is a Node.js HTTP benchmarking tool built by [Matteo Collina](https://github.com/mcollina), one of the most prolific contributors to the Node.js ecosystem. It is fast, scriptable, and installs as a regular npm package, which means no separate binary to manage.
```bash
npm install --save-dev autocannon
```
The simplest possible test from the command line:
```bash
npx autocannon -c 50 -d 30 https://yoursite.com
```
`-c 50` means 50 concurrent connections. `-d 30` means run for 30 seconds. That is already more useful than nothing.
But the real power comes from using the JavaScript API, which lets you script stages, rotate between multiple endpoints, and build a summary report:
```js
import autocannon from 'autocannon';
import { promisify } from 'util';
const run = promisify(autocannon);
async function runStage({ connections, duration, label }) {
const result = await run({
url: 'https://yoursite.com',
connections,
duration,
requests: [
{ method: 'GET', path: '/' },
{ method: 'GET', path: '/about' },
{ method: 'GET', path: '/blog' },
],
title: label,
});
autocannon.printResult(result);
return result;
}
// Ramp up like a real traffic spike
await runStage({ connections: 20, duration: 30, label: 'Ramp-up' });
await runStage({ connections: 50, duration: 60, label: 'Sustained' });
await runStage({ connections: 100, duration: 30, label: 'Peak' });
await runStage({ connections: 20, duration: 30, label: 'Cool-down' });
```
I added three commands to my own `package.json`:
```json
"load:smoke": "node scripts/load-test.mjs smoke",
"load:test": "node scripts/load-test.mjs load",
"load:stress": "node scripts/load-test.mjs stress",
```

Each mode runs a different set of stages. `smoke` is fifteen seconds and five connections, just enough to confirm the site is alive. `load` replicates roughly what Anatoli ran. `stress` goes further, pushing to 300 concurrent connections to find where things start to degrade.
## One Thing to Watch Out For ⚠️
If you are testing a POST endpoint that triggers side effects like sending emails, writing to a database, or charging a credit card, make sure your test payload is designed to fail validation quickly. For my `/api/contact` endpoint, I pass a deliberately invalid `reason` field so the server returns a fast `400` without ever touching the mail transport.
## Reading the Results 📊
`autocannon` prints a table after each stage. Here is what it looks like for a smoke run against this very site:

The numbers to focus on:
- **Req/s** (requests per second): your throughput. Higher is better.
- **Latency p95 / p97.5 / p99**: percentile latencies. Take all your requests, sort them from fastest to slowest, then read off the value at that position. p95 = the response time at position 950 out of 1000. p99 = position 990. The average hides the slow outliers; percentiles expose them. This is called *tail latency*: the long, thin end of the distribution where a small percentage of users are waiting much longer than everyone else.

A p99 above 1000ms means 1 in every 100 visitors waited over a second.
- **Errors / Timeouts**: any non-zero value here deserves immediate attention. These mean your server is dropping or refusing connections.
On the VPS side, watch `htop` or `docker stats` during the test. You are looking for CPU hitting 100% and staying there (a bottleneck), memory growing and not releasing (a leak), and connection count approaching your configured limits.

The CPU trend tells the story clearly: flat baseline, a visible climb as the load ramps up, then recovery once the test ends. Memory stayed stable throughout. No leak. If that CPU line had touched 100% and stayed there, that would be the signal to investigate.
## What I Took Away from All of This 💡
Anatoli did not just run a load test. He demonstrated something I already knew intellectually but had not actually done: you cannot trust your site's resilience without evidence. Assumptions are not SLAs.
The automation is straightforward. The tooling is excellent. There is no good reason to wait for a friend to surprise you with 11,604 requests before you start paying attention to how your site behaves under pressure.
Run the smoke test after every deploy. Run the load test before anything you expect to drive traffic. And if your analytics ever show a path called `/your-friend-was-here`, consider it a gift.

> *Thanks, [Anatoli](https://www.linkedin.com/in/nicolae/). I appreciate you flooding my website with your ~~Spritz~~ Scripts. See you at the office.*
---
# AI in the Browser: No Server, No Costs, No Privacy Trade-off
URL: https://aleromano.com/posts/built-in-browser-ai
Date: 2026-03-30
If you're reading this on Chrome with the right settings, you may have noticed a small "✦ AI Features — experimental" banner at the top of this post. Click it and you'll find buttons to get a *TL;DR*, extract *key points*, or *translate* the post into your language, all without a single byte leaving your browser.

No API key nor costs. No server round-trip. No privacy trade-off.
This is Chrome's **Built-in AI APIs**: small on-device models shipped with the browser, exposed through a native JavaScript API. I spent my evening integrating them into this blog after being inspired by [this talk](https://www.webdayconf.it/e/sessione/5092/AI-nel-browser-costruire-feature-con-le-Built-in-AI-API) at WebDay by [Valerio Como](https://www.linkedin.com/in/valeriocomo/).
This post covers what I learned.
## What Are Chrome's Built-in AI APIs?
Chrome 138+ ships with **Gemini Nano** — a small, efficient language model that runs locally on your machine. Google exposes it through a set of JavaScript APIs:
- **Summarizer API** — summarize text as TL;DR, key points, a teaser, or a headline
- **Translator API** — translate between language pairs using local models
- **Language Model API** (Prompt API) — general-purpose text generation
- **Writer / Rewriter APIs** — draft and refine text
All of these are experimental. They're behind flags in Chrome, require downloading a model component, and the API surface is still evolving. But they're real, they work, and they're genuinely useful.
## Feature Detection: The Right Way
The first thing I got wrong was the detection. I assumed the APIs would live on `window.ai`, like early drafts of the spec suggested. They don't — at least not in Chrome 138+. The actual globals are:
```javascript
'Summarizer' in self // true if Summarizer API is available
'Translator' in self // true if Translator API is available
'LanguageModel' in self // true if Prompt API is available
```
Each API also has an `availability()` method that tells you whether the model is ready:
```javascript
const status = await Summarizer.availability({ outputLanguage: 'en' });
// → 'available' | 'downloadable' | 'downloading' | 'unavailable'
```
- **`available`** — model is ready, use it now
- **`downloadable`** — device is capable, model not yet downloaded; calling `create()` will trigger download
- **`downloading`** — download in progress
- **`unavailable`** — device doesn't meet requirements or feature is disabled
Crucially: always pass `outputLanguage` to `availability()` too, not just to `create()`. Chrome will warn in the console if you don't.
## Summarization
The Summarizer API takes text and returns a summary as a `ReadableStream`. You create a summarizer with a specific type and format:
```javascript
const summarizer = await Summarizer.create({
type: 'tldr', // 'tldr' | 'key-points' | 'teaser' | 'headline'
format: 'plain-text', // 'plain-text' | 'markdown'
length: 'medium',
outputLanguage: 'en',
});
const stream = summarizer.summarizeStreaming(text);
```

One gotcha I hit: the `type` value is **`'tldr'`** (no semicolon, no dash). Early documentation used `'tl;dr'` but that throws a TypeError in Chrome 146. The enum changed. Always check the current [Summarizer API docs](https://developer.chrome.com/docs/ai/summarizer-api).
For key points, I use `format: 'markdown'` since the output is a bulleted list — rendering it as plain text loses the structure. For TL;DR, plain text is fine.
## Translation
The Translator API works similarly. Check availability for a specific language pair, create a translator, and stream the result:
```javascript
const isTranslationModelAvailable = await Translator.availability({
sourceLanguage: 'en',
targetLanguage: 'fr',
});
if (isTranslationModelAvailable) {
const translator = await Translator.create({
sourceLanguage: 'en',
targetLanguage: 'fr',
});
const stream = translator.translateStreaming(text);
}
```
The Translator works at a sentence level. Each chunk it emits is a translated sentence — a **delta**, not a full updated text. This matters for how you render the stream.
## Streaming: Replace vs. Append
This is where I got burned. The two APIs have different streaming semantics — or at least they did at the time I was building this:
- **Summarizer** used to emit the *full accumulated text* on each chunk (replace mode)
- **Translator** emits *sentence deltas* (append mode)

But in my testing with Chrome 146, the Summarizer also switched to emitting deltas. So I ended up using **append mode** for both: accumulate each chunk and re-render:
```javascript
let accumulated = '';
for await (const chunk of stream) {
accumulated += chunk;
renderMarkdown(accumulated);
}
```
If you see only the last sentence in your output, you're probably replacing when you should be appending.

## The UI: Bottom Sheet + Callout
I built two components:
**`BuiltInAICallout.astro`** — a collapsible `` element at the top of every post. It detects available features on page load and shows only the relevant buttons. If nothing is available (Firefox, Safari, older Chrome), it hides itself entirely — no trace in the UI.
**`BuiltInAIBottomSheet.astro`** — a slide-up panel that shows streaming AI output. It uses `marked` to render markdown, auto-scrolls as content arrives, and closes on Escape, backdrop click, or the X button.
A subtle CSS bug I hit: the loading animation used `display: flex`, which overrides the HTML `[hidden]` attribute. The fix:
```css
.loading[hidden] { display: none; }
```

## Privacy
This is the part I find most compelling. The entire feature works offline after the model is downloaded. No request leaves the browser. No text is sent to any server.
The downside: it only works in Chrome, and requires the user to have the Gemini Nano model downloaded. The APIs return `'unavailable'` in Firefox, Safari, and any Chrome without the model. My callout handles this gracefully — it hides itself entirely when nothing is available.
## What I Learned
**The API surface is still moving.** The `'tl;dr'` → `'tldr'` rename caught me off guard. Streaming behavior changed between Chrome versions. Treat these as genuinely experimental.
**Feature detection must be granular.** Don't assume `Summarizer` and `Translator` are always both available. A user might have one model downloaded but not the other. Design your UI to handle every combination.
**On-device AI is slower than cloud AI.** Gemini Nano is not a "normal" LLM. It's a small model optimized for device inference. The summaries are good, the translations are decent, but don't expect the same quality as a frontier model. For the use case here — an optional reader aid — it's more than enough.
**The privacy story is genuinely strong.** For anyone building features where data sensitivity matters, on-device inference is a real option now, not a research project.
The full implementation is open source — you can browse the components and utility module on [GitHub](https://github.com/aleromano92/aleromano.com).
---
# I Mandated AI for My Entire Team. Here's What One Month of Data Says.
URL: https://aleromano.com/posts/ai-mandate-team-survey-results
Date: 2026-02-27
> DISCLAIMER: I don't see my engineers the way they're depicted in the image above. But I loved how Nano Banana portrayed me so I decided to keep it anyway 😁
"You have to try it. No, seriously. Starting next week, everyone uses AI-assisted coding by default. That's it."
I said it in early January 2026, standing in front of my three software engineering teams. On LinkedIn I described myself as having "acted as an authoritarian manager", jokingly. But I wasn't joking that much 😁
I've been writing about how [AI-assisted development changed my own coding habits](/posts/manage-parent-code-ai), and about how [AI became a bridge between ideas and execution](/posts/high-agency-ai-philosophy). But there's a difference between an Engineering Manager coding late on a Saturday and asking his team to change how they work every day. One is a personal experiment. The other is a management decision that affects real people, their craft, and their sense of autonomy.
So I put a number on it: **one month**. We'd run it as a forced experiment, then I'd ask for feedback and let the data speak.
Here's what I discovered.
## 🤔 Why a Mandate Instead of a Recommendation?
I know what you might be thinking: mandates are authoritarian and engineers are professionals who should choose their own tools.
In principle I agree, but I saw "optional adoption" as too risky for the AI-assisted development wave.
I was worried that if I hadn't acted, six months later I'd find a split culture, inconsistent practices and no shared learning.
> The only way to have a genuine team opinion on something is for the whole team to actually experience it.
I wasn't asking them to adopt it permanently. I was asking them to give it a real try, and I committed to listening. That seemed reasonable.
Twelve engineers completed the survey after one month. Here's what they said.
## 🚀 Velocity: Everyone Moved Faster

**100% of the team perceived they moved faster.** Nobody reported the same pace. Nobody reported slowing down.
I was not expecting unanimity. I expected a healthy distribution, maybe some skeptics who felt the review overhead cancelled out the generation speed. Instead: everyone, without exception, felt faster.
Again: I don't have objective data to prove this claim, but for my purposes it was enough to have an impact on people's *perception*.
## 🤨 Code Quality: Holding Steady, With Nuance
The velocity result would be pointless if quality had collapsed. Here's what the team reported:

Two thirds see no quality regression. A sixth even reports improvements. One sixth report lower quality, which tells me the tool is not uniformly good in all contexts, or that some team members haven't yet found the prompting approach that works for them.
This maps to something I said in my post on [context engineering](/posts/manage-parent-code-ai): the quality of the output depends heavily on how well context is provided. One of the open-ended responses from the survey said it directly:
> "Results depend heavily on the prompting approach — we should share best practices as a team."
That's feedback I'm taking seriously. Shared tooling without shared technique is only half the picture.
## ⏱️ The Debug vs. Write Balance Shifted
Here's the result that generated the most interesting conversation with my team:

Half the team is spending more time reviewing AI output than they used to spend writing code themselves. That's a real shift in the nature of the work. And honestly? I think it's mostly healthy.
One of the survey responses put it in a way that stuck with me:
> "Reviewing junior AI-generated code is a great learning opportunity. The code is 'okay' but lacks *taste*."
That phrase, *lacks taste*, is exactly right. AI generates structurally correct code that often misses the subtle, accumulated judgment that comes from years of shipping software in a specific domain with a specific team. The code works. It just doesn't always *belong*.
Which means we have another way to help less experienced people develop their judgment, through code review comments.
## 🛠️ Where AI Actually Helped
I asked the team which tasks they found AI most helpful for. They could select multiple options:

Two tasks tied at the top: **generating unit tests** and **understanding unfamiliar code**, both at nearly 92%.
The unit test result doesn't surprise me. This is where AI consistently shines because tests are well-scoped, have clear success criteria, and free up developer time for the harder judgment calls. I've [written about this before](/posts/manage-parent-code-ai): asking the AI to write tests and then validating them is one of the best uses of the tool.
The "understanding unfamiliar code" result is the one that excites me most. We work on a Java monorepo with years of accumulated context. New joiners now use AI as a codebase navigator. A patient and always-available assistant. One respondent noted:
> "It managed to make consistency checks across 4 repositories and refactored code in a good way."
That said, another respondent added a caveat:
> "It completely missed the purpose of the tests it created — I had to switch to 'manual' mode."
It happens. AI [needs to be able to hallucinate](https://openai.com/index/why-language-models-hallucinate/) to remain such an effective tool.
## 🔍 How Well Does AI Actually Understand Our Codebase?
I asked the team to rate AI's effectiveness at understanding our specific codebase on a 1-to-5 scale.

No one rated it below 4. That's a meaningful signal, especially for a complex enterprise codebase. One respondent mentioned that Opus works great but is expensive on tokens, and that the IntelliJ integration still has rough edges. Tool maturity is still catching up to model capability.
The suggestion that came out of this: invest time in **AGENTS.md, copilot-instructions.md, and CLAUDE.md** tuning.
## 🔐 Security Trust: A Clear Gap
I asked the team to rate how much they trust AI-generated code from a security perspective, on a 1-to-5 scale.

Nobody gave it higher than 3. The mean is somewhere around 2.4/5. Nobody on the team feels confident enough to ship AI-generated code without careful security scrutiny, and I think that's exactly the right instinct. AI models don't have threat models. They don't think about your deployment environment, your user patterns or your data sensitivity. They produce code that *works*, not code that's necessarily *safe*.
## 😌 The Result That Matters Most to Me
Here's the one I keep coming back to. I asked: how do you feel at the end of the day compared to before?

Two thirds of my team end the day less tired than they did before.
I've been a manager long enough to know that **developer fatigue is a real cost**, one that doesn't show up in sprint velocity or ticket throughput. It shows up in the quality of decisions made. In the willingness to take on a hard problem on a Friday. In retention conversations six months later.
The idea that a change in tooling might reduce cognitive load, that technology can actually make the working day feel lighter, is what I find most exciting about this experiment. It aligns with something I genuinely believe:
> Technology should serve human wellbeing, not just productivity metrics.
Velocity is good. Less drained engineers at the end of the day is better.
## 🔮 What the Team Wants Next
I asked: now that you've tried it, what's your preference going forward?

Zero people want to go back. That might be the clearest signal of all.
The split between "AI-first" and "hybrid" is healthy. It reflects different use cases and different comfort levels. My plan going forward is not to impose a single workflow, but to **make the hybrid approach concrete**: clear team guidelines on when to lean on AI heavily, when to write manually, and how to review AI output with the right level of scrutiny.
One respondent asked specifically for **concrete demos of AI workflow using our actual tools**: the Java monorepo, our IDE setup. That's going on the team agenda. Shared technique is table stakes for shared tooling.
## 💡 What I'm Taking Home
Twelve engineers. One month. A few things I now believe more firmly than before:
**The productivity gains are real.** 100% velocity improvement perception is not a rounding error. Something meaningful is happening.
**The fatigue reduction is the underrated story.** Velocity is measurable. Wellbeing is harder to quantify, but the signal here is strong. I want to track this over time.
**The security gap needs a structured answer.** Specific agents dedicated to reviewing this aspect?
## Conclusion 🔁
I mandated AI. I listened to the feedback. I was surprised by what came back.
This was never about forcing a tool on engineers who didn't want it. It was about giving everyone the same real experience before forming an opinion, and then building a shared practice from what we learned together.
The experiment worked. Not so much for the survey numbers, but because we now have a team with shared vocabulary, shared data, and shared next steps. That's what a management decision should produce.
If you're an Engineering Manager sitting on the fence: the tool is mature enough. The real question is whether you're ready to lead the adoption with the same care you'd give any other team practice.
---
# The Boys and the Future of Super AIs
URL: https://aleromano.com/posts/the-boys-future-super-ai
Date: 2026-01-31
The inspiration for this post comes from reading Dario Amodei's essay, [The Adolescence of Technology](https://darioamodei.com/essay/the-adolescence-of-technology). The first section of the essay focuses on factually analyzing the true risks that AI can bring. And no, it's not the disappearance of some jobs, or at least these are not the most imminent or catastrophic dangers. The major risks are linked to potential biological damage that can be realized with the next generation of models.
## The Parallel with The Boys 💉
There is a sentence in the essay that struck me particularly:
> "To put it another way, renting a powerful AI gives intelligence to malicious (but otherwise average) people."
This statement immediately made me think of the comic and TV series *The Boys*, where a private company, Vought International, gave superpowers to normal people through a chemical compound (Compound V). The result? "Douchebag" superheroes who abuse their powers or, worse, create real disasters. In particular, the comic tells of some episodes where Vought pressured the government to insert superhumans into military operations. The outcome was a true disaster, with unspeakable loss of human life, because the superheroes were absolutely not trained for war and its rules, bringing only chaos and collateral destruction.
## The Super-AI Cast 🦸♂️ 🦸♀️ 🧔♂️ 🦹♂️ 🦹♂️
Before returning to examine what we could do to avoid such a scenario coming true, allow me to add another level to this parallel. This time between the "Supes" of The Boys and current AI models:
* **Homelander is Grok**: Anarchic, incredibly strong, and without inhibitions. He is not afraid to go beyond limits, with all the destructive consequences that entail. He represents power without control and a total lack of ethical responsibility.
* **Starlight is Claude**: She realizes the huge problem that "superheroes" pose and wants to collaborate with institutions to put a brake on their overpowering presence. She tries to do the right thing from the inside, pushing for safety and ethics.
* **The Regulator is Butcher**: Having waited too long to take countermeasures, now risks having to resort to "violence" (metaphorically, draconian laws or total bans) and the most brutal methods to restore order. If politics sleeps, the awakening will be abrupt and brutal like Billy Butcher.
* **The Deep is OpenAI**: Don't get me wrong OpenAI, but the recent announcement of putting ads in its responses reminded me of the lost Deep. Once part of the "Greats/The Seven", now he seems to desperately try to remain relevant or profitable, sometimes with moves that leave one perplexed.
* **Soldier Boy is Gemini**: Just as Soldier Boy was the number one hero before Homelander, Google (with DeepMind, AlphaGo, BERT) was the undisputed king of AI before the ChatGPT hype arrived. Soldier Boy doesn't just have brute strength, he has that devastating chest beam that "burns" Compound V out of others. Google has computational power (TPUs, Data Centers) and a database (YouTube, Search) that could technically "shut down" or render competitors obsolete if it decided to release its entire arsenal without ethical brakes.

## The Real Enemy: Vought International 🏢
In *The Boys*, the real villain is not a single crazed superhero, but **Vought International**: the corporation that creates them, manages them, and covers up their disasters to protect the value of its shares.
In the AI world, the "Vought dynamic" is the unrestrained race for profit and market dominance. Even companies with the best intentions (the "Starlights") are forced to run faster and faster because the market punishes those who slow down. If Google doesn't release Gemini immediately, OpenAI takes the whole market.
And us? We are the citizens who applaud at Homelander's rallies. We are seduced by the convenience of having emails or code written by AI, ignoring that we are handing the keys of our society to entities that, like Vought, answer first to shareholders and then to public safety.
## Back to Risks: A Practical Scenario ☣️
Amodei highlights how the next generation of AI models will have practical capabilities that go far beyond simple text or image generation. These models will be able to perform complex tasks, such as designing new drugs, creating advanced materials, or even manipulating biological organisms.
Imagine the park drug dealer with ambitions to be Pablo Escobar who finds himself with a tool that advises him step-by-step on how to better run his business: from advanced money laundering techniques to the creation of bacteriological weapons to blackmail governments or eliminate competition.
If you think I'm exaggerating because "even before with Google you could have done it, the information was there", you are dead wrong. Sure, genomes and much scientific information are publicly available, but Google never gave you the "tacit knowledge" to put the pieces together effectively. LLMs do. To delve deeper, I recommend reading Anthropic's Red Team report on biological risks: [Anthropic Red Team Report](https://red.anthropic.com/2025/biorisk/).
## Regulation vs Far West 👨⚖️ 🤠
Amodei suggests that AI companies should collaborate and work with governments to establish a series of "guard rails" for models. It is not about invoking excessive and generic legislation that stifles innovation, but very specific and targeted norms, to be implemented as new concrete risks emerge.
In this context, Amodei highlights how Anthropic is already setting up these countermeasures autonomously, without any regulator having asked them to. On the other hand, other AIs have already proven to allow serious abuses, such as the generation of non-consensual nude images, including of common people and children ([see this article from The Guardian](https://www.theguardian.com/commentisfree/2026/jan/09/grok-undressing-women-children-us-action)).
## Conclusion 🧘♂️
I wouldn't want to live in a world where politics has to create "Butchers" to stop AI. I strongly hope that policy makers read this essay by Amodei seriously and start collaborating with companies in a serious manner.
We need to avoid over-regulation in the style of a preventive "EU AI Act" on everything, because all of humanity needs the progress that this technology can bring (in medicine, science, energy). But at the same time, we desperately need targeted, agile, and fact-based regulation, not based on fear or ignorance. Only in this way can we have the benefits of superpowers without ending up crushed by Vought's collateral damage.
---
# High Agency and Philosophy in the Age of Artificial Intelligence
URL: https://aleromano.com/posts/high-agency-ai-philosophy
Date: 2026-01-12
It's Christmas Eve morning 2025 and I'm working. My plan is to "empty" my brain into a strategy document for 2026, taking advantage of being free from meetings.
My plan gets interrupted by a service that started throwing a strange error I can't ignore.
The problem is, being an [Engineering Manager of 3 teams](/posts/manage-parent-code-ai), I don't touch the projects directly even though I know roughly what they do. I was one step away from disturbing the engineer who usually handles it, but he was on well-deserved holidays after an intense period.
So I decided to try using AI again for work projects. Until then, I had only used it successfully on small personal projects like [this website](/posts/about-this-site), but I had always struggled to use it on monorepos and large professional projects like those at Mollie.
I fed Claude Opus 4.5 the exception and project context, using Copilot from the CLI.
**In less than 20 minutes**:
- Claude had written the fix in just a few lines of very readable code;
- I had understood the project better, its software architecture, and why that exception had occurred;
- I had opened the PR waiting for the CI pipeline to pass
Shortly after, I released, verified the error was gone, and spent a peaceful Christmas, but with a new awareness: **AI is ready for large professional projects too.**
During the holidays, I read a lot about it and realized this feeling is common. Towards the end, a new question arose in me: if producing *good quality* code is now a commodity accessible to everyone, **what's left for Software Engineers?**
## 🎯 High Agency
While reflecting on this awareness, my brain pulled out from the drawer of memories an essay I had read a few months ago: [High Agency in 30 minutes](https://www.highagency.com/) by George Mack.
The author opens with a provocative question:
> You wake up in a prison cell in a third world country. You can only call one person you know to get you out. Who do you call?
The person you choose has something special. A spark. That "something" is **high agency**.
Mack defines it as the combination of three skills rarely found together:
1. **Clear thinking**: without this, you rush into the first bad plan that comes to mind
2. **Bias towards action**: without this, your ideas never leave theory
3. **Disagreeability**: without this, you give up when some authority (your boss or *your wife*) tells you "No"
I found many references to my philosophy readings, a subject I'm catching up on recently, not having attended classical high school. And I realized these concepts aren't new: Greek and Roman philosophers had **already figured them out two thousand years ago**.
## 🏛️ Plato's World of Forms
Plato spoke of the **world of forms**: a non-physical plane where perfect ideas of things exist. The ideal chair. Perfect justice. Absolute beauty. What we touch in the real world? Just imperfect copies of those forms.
The article says ideas are "abundant" and even "useless" without execution. This mirrors the Platonic view: until an idea descends from the realm of thought into action, it remains abstract, sterile. **A startup idea**, like the idea of a chair, **means nothing until it's built.**
## ⚡ Aristotle and Actualization
Aristotle introduces the concept of **potentiality (dynamis)** vs **actuality (energeia)**. A block of marble has the *potential* to be a statue. But it only becomes a statue through the sculptor's action.
Translated to our world: you can have the perfect product idea in your head, but until you start building it, **you just have marble.**
## 🔥 Seneca and the Urgency of Action
Seneca was obsessed with the gap between conception and action. For him, philosophy wasn't armchair theorizing, it was a practical tool for living. He wrote:
> "It is not that we have a short time to live, but that we waste a lot of it."
My smartphone trembled in my hand when I read it: guilty as charged, your honor.
The article distinguishes between "talkers" and "doers". Seneca would say that ideas without execution waste *time* and *life*. He would have endorsed the High Agency principle: **prioritizing movement through resistance**, meaning choosing execution over comfort or perfection.
## 🤖 AI as the Bridge Between Two Worlds
This is where AI changes everything.
Before Claude, Cursor, Copilot, turning an idea into code required:
- Years of practice
- Deep knowledge of languages and frameworks
- Hours of debugging and Stack Overflow
- An extremely high frustration threshold
Today? **AI has become the bridge between the world of ideas and the real world.** It's the universal chisel that allows anyone to transform potential into actual.
But, and here's the point, the chisel alone sculpts nothing.
## 🚀 The Real Differentiator
If everyone has access to the same magic chisel, what separates those who create from those who just watch?
**High agency.**
High agency means not waiting for the perfect moment (spoiler: it doesn't exist). It means being responsible for the outcome instead of being at the mercy of circumstances. It means that when someone tells you "no", that's the beginning of the conversation, not the end.
AI gives you the chisel. But you're the one who must decide to get up, grab that block of marble, and start sculpting.
## 💡 Conclusion: It's Never Just Writing Code
Putting the pieces together:
- **Plato** explains where ideas live (in the abstract plane)
- **Aristotle** explains how they become real (through action)
- **Seneca** urges you to do it NOW, or you're wasting your life
- **AI** tears down the last technical barrier
In a world where writing code is accessible to everyone, you need to focus on what brings an idea to execution. And it's almost never just writing code.
This is why I believe the future belongs to **Product Engineers**: people who own a feature or product end-to-end. For them, code is just *one* of the tools in the drawer. They talk to users. They understand the business. They design the experience. They measure the impact. And yes, they write code too, or rather, they have AI write it.
It's the vision. It's the perseverance. It's the ability to ask the right questions. It's continuous iteration. It's the courage to release something imperfect and improve it.
I want to share some optimism about what awaits us as Software Engineers in the AI era: now is the time to dream big, but above all to do. Because "doing" has never been easier.
After all, staying optimistic is the most logical and pragmatic thing to do, as I say in [my story](/about).
---
# I Manage, I Parent and I Code. Fifteen Minutes at a Time
URL: https://aleromano.com/posts/manage-parent-code-ai
Date: 2025-10-18
It's Saturday afternoon. After a nice seafood spaghetti lunch, my wife and I put the kids down for a nap. Two uninterrupted hours to work on my site! Ten minutes later... "Dad, I need to poop!" End of focus and, until a few months ago, end of my motivation to write code.
At work I'm not interrupted by sentences like that, but as an Engineering Manager for 3 teams, developing people occupies most of my time, with continuous context switches and ad‑hoc chat requests.
Don't get me wrong: **I love my job as a manager and I love being a father**. But I also love writing code and designing software. Trying to stay sharp as a developer under these conditions was a challenge I kept losing, until about a year ago.
## AI-Assisted Development (no, I don't do vibe coding) 🤖
I've seen first-hand how important uninterrupted blocks of time are for a Software Engineer because writing code requires deep focus.
This concept [isn't new](https://www.paulgraham.com/makersschedule.html), but it's too often forgotten, especially by those who schedule meetings right in the middle of a morning or afternoon (and [maybe they're not even effective](/posts/effective-meetings-agenda)).

Then **AI-assisted development** arrived.
When Emma was born I used my month of parental leave to [rebuild this site](/posts/about-this-site) using Astro, TypeScript and Docker, technologies I only superficially knew or hadn't used actively in a while.
I started with Cursor, though now I mostly use GitHub Copilot in VS Code in *Agent* mode.
Let me stop you right away: I don't do *vibe coding*. Every piece of code produced is reviewed, sometimes modified, and always **understood** by me. If something's unclear I switch to *Ask* mode and keep asking questions until I understand the reasoning behind the choices.
So, Sonnet & Co. didn't "build the site for me"; it allowed me to develop consistently even though I was rusty on TypeScript syntax and didn't fully know Astro and Docker, while an unexpected diaper change or my other child's desire to play interrupted the workflow.
## It Feels Like I'm Programming in "English" ✍️
I had a big AHA moment when I started dictating my prompts using the microphone icon in VS Code.

> "I want to build a section of the site listing my most recent posts from X. The API call must happen server-side so the page using this component should be server-side rendered. Split responsibilities into a function handling the REST call, one applying a caching layer, and one adapting data for rendering in a pure presentation component with no logic. Expect the API key as a secret from the environment. Write unit tests for these components. Ask me clarifying questions if you need more detail."
That's similar to the prompts I use (though recently I added a template in the [copilot-instructions.md](https://github.com/aleromano92/aleromano.com/blob/main/.github/copilot-instructions.md)).
I remain the primary expert on the system: I guide design, request certain patterns, communicate functional and cross-functional requirements. I launch the prompt and go do what my team or my family needs. When I return I find a *diff* ready and can quickly validate whether the output matches the intention; otherwise I refine the prompt or tweak things manually.
Effectively, instead of giving the "computer" TypeScript as input, I'm writing in a **higher-level language**: English.
Obviously I do NOT generate commit messages with AI, I write the *why* myself ([here I explain why that's important](/posts/git-commits-why)).
## ~~Prompt~~ Context Engineering: it's like working with a Junior 🎯
"Prompt engineering" is a misleading expression: you're not engineering a prompt; you're **providing context** to an assistant helping you accomplish a complex task. It's honestly not that different from onboarding a (particularly junior) new hire when asking them to build their first features:
- Provide as much business context as possible so decisions align with goals
- Share examples of similar tasks such as past PRs or code areas to mirror
- Ask them to proceed in small steps so you can course-correct early
- Stay open to clarifying further doubts
The **quality of the output** depends on the skill of whoever performs the task, but also on how context is provided: like with ML models, *garbage in = garbage out*.
Another way to improve output quality? **You write the unit tests.** Allow the Agent to run the test command (like `npm test`) automatically until they pass. In the end, tests are an extremely precise way to encode requirements.
## Risks and Caveats ⚠️
I don't think _"I copied from StackOverflow"_ is much different from _"the AI wrote the code for me"_. The real difference is always your __degree of understanding__ of the code pasted (or generated).
Before, with some StackOverflow answers, you might risk rude responses, now you can use an LLM to explain the why behind every choice. And trust me: it'll always tell you it's a great question. It wasn't safe to paste code without understanding before; it's not safe to do vibe coding now.

Some say they're worried that writing less code will erode **muscle memory**, especially around syntax. That's potentially true, just like since digital maps exist our orientation skills have degraded. But we don't stop using maps because the overall benefit massively outweighs the cost.
And several studies confirmed that [learning improves when writing by hand](https://pmc.ncbi.nlm.nih.gov/articles/PMC11943480/), yet I (thankfully) don't see anyone writing programs on paper anymore like in high school 😁
It's important though to be aware of how dependent we already are. If I were deprived of AI today, I'd struggle to be as productive as I am now.
> Remember to commit often, hallucination is around the corner!
### If You're a Junior 👶
If you haven't mastered fundamentals like:
- Data structures
- End-to-end flow of a web request
- Design patterns
- Inheritance vs composition
- Storage systems (databases, caches, buckets)
AI will make you go fast on a *road whose signs you haven't read*.
Long-term risks:
- Difficulty debugging
- Lack of architectural *taste* (knowing what's over-engineered)
- Poor readability
- Misunderstanding how complex systems work (not even necessarily distributed)
I wouldn't ban it, but you must use it to **learn**, not to speed past the basics. Computer Science is still young, you still have time to understand how an x86 CPU and its registers work before jumping back up to high-level things like Docker.
## Conclusion 💡
I realized AI-assisted development also has an emotional benefit: even with a packed schedule (work and family), I can touch my site, or work projects, almost every day. That creates continuity which fuels motivation and satisfaction. I've rediscovered the joy of writing code. 🤗
Even as a Senior Engineering Manager overseeing multiple teams including other managers, I still believe staying hands-on as a developer makes me more effective in my role.
---
# 3 Career Tips Nobody Talks About
URL: https://aleromano.com/posts/3-career-tips
Date: 2025-08-17
In my years of experience in the tech industry, I've worked for companies with structured promotion processes and others that went by _gut feeling_. Incredibly though, the profiles of people who got promoted tended to have some common characteristics.
When I read or hear Software Engineers say things like "I want to grow as an Individual Contributor, so I decided to study Rust to aim for a promotion," I naturally smile and think to myself that this plan is unlikely to end well. Don't get me wrong, there are tons of things you can learn from studying a new programming language, but there are other things to focus on that could have much more effect in getting you that promotion.
## 1. Understand What Your Company Rewards
**The first tip: every company has its own evaluation criteria.**
There are companies that value people who create new features/products and follow their complete lifecycle, from ideation to production. An example is Google where especially for certain roles you're "forced" to launch new products to get a promotion. In fact, it's full of products that once the Individual Contributor who pushed for them has exhausted their purpose, end up [abandoned and discontinued](https://gcemetery.co/).
Other companies instead reward those who improve existing processes, like Amazon, where the focus is on efficiency and continuous optimization.
Still others look with admiration at those who simply manage to save the massive costs of the Cloud.
Learn to understand where you are, talk to people who have been recently promoted and ask what skills or results led to their promotion. Remember, every company is different.
### Career Frameworks
If your company has a Career Framework, it's another good starting point. But be careful: often these documents are too generic. They have to be, since they need to fit an entire Engineering department into various levels: words like "complexity," "distributed systems," "high impact" abound in the text without giving you a measuring stick [(even though they should)](https://skamille.medium.com/10-years-of-engineering-ladders-329d309000cd).
My advice is to ask your manager to translate those principles into concrete and practical examples. Your Manager plays an important role in your growth and is the first person to judge whether you're ready or not for the next step: it's fundamental to understand the _interpretation_ they give to those principles.
Ask specific questions:
- "What exactly does 'technical leadership' mean for my role?"
- "Can you give me an example of a project that demonstrates 'business impact'?"
### Business Impact
This is a common denominator across all companies. Yes, I explained earlier that different companies reward different behaviors, but having the description of business impact in your Promo Case is an evergreen that any manager likes.
The most important thing you need to do is **quantify it**.
Some examples of how to transform vague descriptions into measurable impacts:
- don't say "I improved performance," say "I reduced response times by 40%, generating an estimated savings of X euros per year."
- avoid "I increased system resilience," explain how "after my refactoring, the number of production incidents on that component decreased from 4 per month to 1 every 3 months."
- rewrite "I released the new feature X that sped up analysis time for Operations people" by citing how "the new feature X allows handling 10% more cases in the same time, allowing us to grow without needing to hire more in the Operations department."
#### Glue Work: Essential but Not Quantifiable
Not everything needs to be measured quantitatively. There's what's called [**glue work**](https://www.noidea.dog/glue): mentorship, unblocking other colleagues, preparing refined tasks, finding cross-team solutions, facilitating communication between departments. It's fundamental for business success but difficult to measure.
For glue work, you need to be good at making your manager aware that you're doing it, and they need to be good at valuing it in a potential promo case. How? Document it in your Brag Journal and ask for peer feedback from the people you consistently help.
#### Tech-for-Tech Projects: Impact Through Execution
Even purely technical projects without direct business impact have value, but you need to highlight the quality of your execution:
- "I migrated 30 services from RabbitMQ to Google Pub/Sub by first creating a pattern/strategy that made migrating the other 29 services 60% faster"
- "I completed the migration with zero incidents and downtime in production"
- "I created a library for this migration that was adopted by 5 other teams in the company"
- "I did continuous mentorship so that this complex migration was delivered by 2 junior developers (deliver through others)"
### Understand How Your Manager Is Evaluated
It's not just about getting them to delegate parts of their work to you, although that is a way for you to grow and try new challenges. Their objectives become opportunities for you to grow and help them reach their targets.
**Actively help your manager pursue their objectives.** If their success depends on team retention, propose and implement initiatives to improve employee experience. If they need to accelerate delivery, focus on automation and efficiency. If they need to reduce operational costs, find ways to optimize infrastructure.
This approach has two advantages: quickly earning their trust and helping you think _big_, at a level of impact higher than the one you currently operate at. This is true even if you want to grow as an Individual Contributor without pursuing the Manager path.
## 2. "I'm on it": The Power of Responsibility
**The second tip: promotions follow responsibilities, they don't precede them.**
I've seen too many colleagues wait for the title before acting like a senior or lead. It's the wrong approach. First you take on responsibility and demonstrate impact, then comes the recognition.
When you say "I'm on it" and keep your promise, you build a reputation for reliability. When you solve problems without being assigned them, you demonstrate proactivity. When you actively seek more responsibility, you signal healthy ambition.
Have you ever read in a group chat about an incident a Senior Engineer respond "I'm on it"? I have, both as a manager and as a simple colleague, and what I usually felt was a mix of serenity and admiration. Serenity because I knew the problem would be solved, admiration because that person had taken the initiative without waiting to be asked. And I had even more desire to help them.
Remember: **you are the main person responsible for your career.** Managers change, company priorities shift, but your growth depends on you.
"I'm on it" also applies to your career.
### Make Your Work Visible
The best work is useless if nobody knows about it. Some practical ways:
- **Documentation**: write decision records, post-mortems, technical guides and keep a [Brag Journal](https://jvns.ca/blog/brag-documents/) (even private)
- **Presentations**: share the results of your projects in team meetings, whether it's a demo or a review. No need for PowerPoint, just sharing your screen works
- **Asynchronous communication**: update regularly on progress, don't wait for them to ask
- **Internal blog posts**: share solutions and best practices
- **1-on-1s**: don't assume your manager knows everything you're working on. Tell them about it
### Keep Your Promises
**Always.** When you can't keep a promise, communicate proactively. Transparency builds trust, silence destroys it.
If you told someone you would release something on a certain day and you realize you're running late, write to them as soon as you realize it, tell them why and propose a new date. Don't wait for them to ask you about it.
I also really appreciate people who simply tell me: "you asked me something complex that I need time to think through an answer, let's touch base by Friday morning, I'll send you a meeting invite."
## 3. Ask for Feedback and Return Value
**The third tip: feedback is a super-power, if you know how to use it.**
Not all feedback is useful. Learn to filter: ask for advice from people you respect and who have achieved results you admire. But don't limit yourself to engineers.
Realize that asking for feedback requires an investment (of time and energy) from the person who has to write it: thank them and, if you found it useful, explain what you intend to do to improve. This also helps you commit more seriously because you've [shared your plan with someone](https://zenhabitsbook.com/42-chapter-6/).
### The Power of Non-Technical Feedback
Some of the most valuable feedback comes from:
- Product managers
- Designers
- Customer success
- Marketing and sales colleagues
They see your work from different perspectives and can highlight blind spots that technical colleagues don't notice.
A PM who advises you to be clearer in your demos and use storytelling techniques is worth as much as a Senior Engineer who suggests you study JVM internals.
### External Validation
Participate in meetups, give talks, contribute to open source projects. External validation gives you internal credibility and exposes you to different perspectives.
It also helps you create a sort of **portfolio** of your skills and experiences, which can prove useful in the future, especially if you want to change jobs.
### Be a Mentor
Stop for a second and think about who you consider to have been your first mentor or the most significant one in your career. That person who made you _turn the corner_. Feel the emotions you experience when you think of him/her. Now ask yourself: how can I do the same for someone else?
Few things are as important in your growth as helping others do the same. **Be a mentor.**
## Conclusions
Before you close this tab, I want to go against what I was saying at the beginning: learning a new programming language, sometimes, is the right thing to do to grow. Just like studying Design Patterns or automated testing.
But I believe the Internet is full of people promoting this as if it were the only thing needed in an Individual Contributor career.
It's not like that, or at least there are very few companies in the world that reward only this component without all the rest I've told you about.
Happy growing! 🚀
---
# This Site and My Return to Basics
URL: https://aleromano.com/posts/about-this-site
Date: 2025-04-10
Few people in the world are constantly searching for the "next big thing" as much as Software Engineers :smile:
Whether it's frameworks, SaaS, languages, or platforms, FOMO (Fear Of Missing Out) drives us to stay updated and try new things, paying a significant cost in terms of time. However, I realized that in trying to keep up with the latest trends, I was losing sight of the basics that make it all possible.
Even in my [bio on this site](/about#biography), I wrote that I like to understand how things work. But as I used increasingly high-level tools, I became "scared" of finding myself so far removed from how these technologies actually work under the hood. I started feeling the need to return to a more "manual" and "primitive" approach to better understand the foundations upon which everything we do is built.
## Back to Basics with a VPS 🏗️
Recently, I decided to host this site on a **VPS** instead of using services like Vercel or Netlify. Don't get me wrong: these platforms offer incredible developer experiences that simplify deployment and hosting. But I realized I was gradually unlearning the basics of how the internet actually works.
Configuring a server, setting up nginx, managing SSL certificates, and handling deployments manually felt like diving back into my 18-year-old self. These are fundamental skills we often take for granted in the era of one-click deployments.
I chose Hetzner as it seemed the best value for money: I'm using a CX22 instance with 2 vCPUs (Intel), 4GB RAM, and 40GB SSD for €4 per month!
> If you want to try it, you can [use my link](https://hetzner.cloud/?ref=5R5wQFCPotUP) to get €20 in credits! 🚀
## Staying Hands-On 🧑🏭
As someone who transitioned into engineering management, it's easy to drift away from the technical details. However, I believe staying hands-on with technology is crucial; not just for maintaining credibility with my teams, but also for my personal satisfaction and continuous growth.
This site is my playground: a place where I can experiment, learn, and implement without worrying too much about breaking everything!
## AI-Assisted Development for the Time-Strapped 🤖
Being an Engineering Manager for three teams and a father of two doesn't leave much room for long coding sessions. The time I can dedicate is in small, often interrupted blocks, without hours of continuous focus.
This is where AI-assisted development made a difference. I used [Cursor](https://www.cursor.com/) to speed up my workflow ([learn more](https://aleromano.com/posts/en/manage-parent-code-ai)), allowing me to make significant progress even in short, fragmented time blocks. The ability to express an intention and have the AI implement it for me in *Agent* mode reduced the energy cost of constant context switching.
## Tech Stack: Simplicity Matters 💻
For this site, I chose a simple yet powerful stack. I'm using [Astro](https://astro.build/) as the framework. It's perfect for content-focused sites thanks to its excellent static site generation capabilities. Then, although overkill for a solo project, I used **TypeScript**.
The post content, like this one you're reading, is written in **Markdown** and managed via Astro's `Content Collections`, making content organization and updates very straightforward. For styling, I opted for **pure CSS**, using CSS variables to implement features like light/dark theme, maintaining direct control over the appearance without additional dependencies.
I also implemented **multilingual support (i18n)** to reach a broader audience and an **RSS feed** to allow users to follow updates via their favorite readers.
Although not strictly necessary, I decided to use [Docker](https://www.docker.com/) for packaging and deployment. For a purely static site like this, it smells quite a bit like over-engineering, but:
- Docker is an industry standard I've rarely dealt with. Plus, a user [on X intrigued me with his posts](https://x.com/kkyrio/status/1861371736492572710) on the topic.
- I also wanted to use [Nginx](https://nginx.org/) to efficiently serve static content, so Docker Compose seemed the right choice.
Deployment happens on every commit to `main` via GitHub Actions, which run tests and build the site, then send everything to the remote server.
## The Cost Factor 💰
A motivation, albeit secondary, was also economic. I didn't want to spend $16 a month for [Super.so](https://super.so/) when I knew I could build and host something myself for a fraction of the cost. My VPS costs significantly less (€4.62 per month), and I have the added advantage of being able to host multiple projects on the same server (my next SaaS is just around the corner, too bad the Earth is round 😂).
## Should You Do It Too? 🤔
**TL;DR:** No. Unless your context and passions are similar to mine.
This approach isn't for everyone. If you're focused on rapidly shipping products or have no interest in the infrastructure side of web development, platforms like Vercel and Netlify are absolutely the right choice.
But there's a deep, intrinsic value in going back to basics and truly understanding how things work. Yes, even LLMs. As much as we're bombarded with micro-information and novelties, it's crucial not to lose sight of the foundations upon which everything we do is built.
Sometimes, to move forward, we need to take a step back and revisit what we might have forgotten along the way.
---
# Improving My Blog With Claude Code
URL: https://aleromano.com/posts/improving-blog-with-claude
Date: 2025-02-25
You know those small tasks that keep moving from one to-do list to the next? For months I've been meaning to fix the inconsistent tags on my blog but kept pushing it off. I had a perfect storm of messy tags - different formats, random abbreviations, and duplicate concepts across posts. Last weekend, I finally tackled it, but with a twist: I enlisted the help of Claude, an AI coding assistant.
## My Blog Tag Problem
As my blog grew over time, my tagging system became increasingly inconsistent:
- Inconsistent formatting: `remote-work` vs `Remote Work` vs `remoteWork`
- Overlapping concepts: `DX` and `Developer Experience` used interchangeably
- Abbreviated tags that weren't obvious to readers
- Inconsistent translations between Italian and English versions of posts
This made it difficult for readers to find related content and complicated my own content management.
## Enter Claude Code
[Claude Code](https://www.anthropic.com/claude) is Anthropic's AI coding assistant. It can analyze codebases, suggest improvements, and implement changes across multiple files. I decided to see if it could handle my tag refactoring task.

Here's how the process went:
1. **Analysis**: Claude scanned my entire blog repository, analyzing all posts in both English and Italian to understand the existing tag patterns
2. **Tag standardization plan**: It identified inconsistencies and suggested PascalCase with spaces as the best format
3. **Implementation**: Claude updated all blog post tags across the codebase in a consistent manner
4. **Enhanced organization**: It even created utility functions to group related tags
## The Results
Here's what the tag transformation looked like for some posts:
- `["Tech","DX", "Productivity"]` → `["Tech", "Developer Experience", "Productivity", "Tools"]`
- `["Productivity","Async Work"]` → `["Productivity", "Remote Work", "Leadership"]`
- `["Wellness","DX","Tech","Inspiration"]` → `["Wellness", "Developer Experience", "Tech", "Inspiration"]`
- `["Inspiration","Coaching","1to1"]` → `["Inspiration", "Coaching", "One To One"]`
Beyond just reformatting, Claude suggested semantic improvements, expanding abbreviations, and ensuring consistent tag usage across related posts.
## Beyond Just Fixing Problems
The impressive part wasn't just that Claude fixed the tags, but that it understood the broader context of what I was trying to achieve. It spotted patterns I hadn't explicitly mentioned and suggested improvements I hadn't considered.
My favorite was when it recognized that "DX" is actually "Developer Experience" and consistently made this change across all posts, making my content more accessible to readers who might not know all the tech abbreviations.
## Why This Matters
Taking the time to clean up tags significantly improves the user experience for my readers. They can now:
- Discover related content more easily
- Navigate between topics more intuitively
- Find consistent terminology between English and Italian posts
## The AI Coding Experience
The whole process felt surprisingly natural - like working with a really fast colleague who never gets bored with repetitive tasks. Instead of tediously opening file after file to fix tags, I just explained what I wanted in plain language and reviewed the results.
I was particularly impressed by how Claude:
1. Figured out my codebase structure without me explaining it
2. Made consistent changes across all posts (at least 20 files)
3. Didn't just blindly follow instructions but suggested better approaches
4. Balanced technical correctness with conceptual organization
It reminded me of my [Mac to Windows transition](/posts/mac-to-windows) where the right tools turned a dreaded change into something actually enjoyable.
## Conclusion
Using Claude Code to refactor my blog's tagging system saved me hours of tedious work. More importantly, it produced a better result than I likely would have achieved manually, with added utilities for maintaining consistency in the future.
If you have a similar refactoring task that has been lingering on your to-do list, consider giving an AI coding assistant a try. Just be clear about what you want, review the changes, and you might be surprised by the results.
The future of web development certainly involves this kind of human-AI collaboration, combining your domain expertise with AI's ability to handle repetitive tasks with consistency and attention to detail.
> This post was written with the help of Claude Code, the same AI assistant that helped me refactor my tags. I simply asked it to document the process we just completed, and it drafted this post based on our conversation. Talk about dogfooding!
---
# Simple VPS Observability for Your Personal Website
URL: https://aleromano.com/posts/simple-vps-observability
Date: 2025-02-24
Setting up a personal website is just the beginning. Keeping it running smoothly is the real challenge. I've learned this the hard way: nothing's more frustrating than discovering your site has been down for hours (or days!) because you didn't have a monitoring system in place. 🤦♂️
Let me share the lightweight observability solution I've implemented for my personal website. It's simple, effective, and doesn't require expensive tools or complex setups.
## Why I Didn't Need Enterprise Monitoring 🔍
For personal websites or small projects, those fancy enterprise-grade monitoring solutions that cost hundreds of dollars monthly are overkill. But you still need to know when something breaks. The key is finding the right balance.
My approach focuses on three critical aspects:
1. **Container Status Monitoring**: Is Docker running my containers? 🐳
2. **Error Log Detection**: Are there hidden errors I should know about? 📋
3. **Website Availability**: Can visitors actually see my site? 🌐
This lightweight approach gives me peace of mind without the complexity of more sophisticated solutions.
## The Solution: A Simple Node.js Daemon ⚙️
I've created a Node.js daemon that runs as a systemd service on my VPS. It performs regular checks and sends notifications via Telegram when issues are detected.

### Key Components
#### 1. Container Status Monitoring
The script regularly checks the status of Docker containers using the `docker ps` command. If any monitored container stops running, I get an immediate alert:
```javascript
const checkContainerStatus = () => {
exec('docker ps -a --format "{{.Names}}|{{.Status}}"', (error, stdout, stderr) => {
// Process container statuses and send alerts for stopped containers
});
};
```
#### 2. Error Log Detection
The daemon scans Docker logs for error messages, exceptions, and fatal errors. This helps me catch issues before they escalate:
```javascript
const checkDockerLogs = () => {
// For each container
exec(`docker logs --since ${since} ${containerName} 2>&1 | grep -i "error\\|exception\\|fatal"`, (error, stdout, stderr) => {
// Send alerts if errors are found
});
};
```
#### 3. Website Availability Checks
Regular HTTP requests verify that my website is accessible and responding properly:
```javascript
const checkWebsite = () => {
const request = https.request(options, (response) => {
const responseTime = Date.now() - startTime;
if (response.statusCode >= 200 && response.statusCode < 400) {
log(`Website is up. Status: ${response.statusCode}, Response time: ${responseTime}ms`);
// ...
// Handle timeouts and errors
};
```
### Notifications via Telegram
All alerts are sent to Telegram, so I get real-time notifications on my phone or desktop:
```javascript
const sendTelegramNotification = async (message) => {
const { botToken, chatId } = CONFIG.telegram;
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
// Send formatted HTML message to Telegram
};
```
## Real-World Monitoring in Action 🚨
When everything is running smoothly, the daemon performs its checks silently in the background. But when something goes wrong—like when Docker containers stop—the system springs into action:

In this example:
1. The monitoring system detected that the Docker containers were stopped
2. It immediately sent a notification with details about which containers were affected
3. It also detected that the website was unreachable as a result
4. The HTML formatting makes the alerts easy to read at a glance
This real-time feedback allows me to quickly fix issues, often before visitors even notice them.
## Why This Approach Is Enough for Me ✅
### 1. Focused on What Matters
My solution covers all these bases without unnecessary complexity.
### 2. Low Resource Consumption
The monitoring daemon uses minimal CPU and memory resources, making it perfect for running alongside my website on the same VPS without impacting performance.
### 3. Zero External Dependencies
Unlike SaaS monitoring solutions, this approach doesn't rely on external services that might introduce additional costs or points of failure. Everything runs on my own infrastructure.
### 4. Immediate Notifications
When something goes wrong, I know immediately through Telegram notifications, allowing me to address issues promptly.
### 5. Easy to Extend
The modular design makes it easy to add additional checks or modify existing ones as my needs evolve.
## Implementation Challenges and Solutions 🛠️
During implementation, I encountered and solved several common challenges:
### 1. Duplicate Logging
Initially, I noticed duplicate log entries because systemd was redirecting both stdout and stderr to the same log file. I fixed this by modifying the logging function:
```javascript
const log = (message, level = 'INFO') => {
// Only log to console if we're not also logging to a file
if (!CONFIG.logFile || process.env.NODE_ENV === 'development') {
console.log(logMessage);
}
// Log to file if specified
if (CONFIG.logFile) {
fs.appendFileSync(CONFIG.logFile, logMessage + '\n');
}
};
```
### 2. Website Timeout Issues
I enhanced the website check to handle timeouts more gracefully and added a fallback check using curl to verify if the issue was with my monitoring or with the website itself:
```javascript
request.on('timeout', () => {
// Try a simple curl command to see if the website is reachable
exec(`curl -s -o /dev/null -w "%{http_code}" -m 10 ${CONFIG.website.url}`, (error, stdout, stderr) => {
// Compare results and send appropriate notification
});
});
```
### 3. HTML Formatting in Notifications
I fixed issues with HTML formatting in Telegram messages by properly configuring the API request:
```javascript
const postData = new URLSearchParams({
chat_id: chatId,
text: message,
parse_mode: 'HTML'
}).toString();
```
## Installation and Setup 📦
The solution includes an installation script that:
1. Checks for Node.js and installs it if necessary
2. Prompts for Telegram bot token and chat ID
3. Sets up a log file with proper permissions
4. Creates and configures a systemd service
5. Starts the monitoring daemon
This makes deployment straightforward even if you're not a Linux expert.
## Get the Complete Solution 📂
The complete solution, including the monitoring script, installation script, and documentation, is available on my GitHub repository. Feel free to use it, fork it, or adapt it to your own needs:
[github.com/aleromano92/aleromano.com/tree/main/scripts/observability](https://github.com/aleromano92/aleromano.com/tree/main/scripts/observability)
I've made everything open source so you can benefit from my experience and avoid the same challenges I faced. If you have any improvements or suggestions, pull requests are welcome!
## Conclusion
The goal of observability for a personal website isn't to collect every possible metric, but to ensure you know when something needs your attention. This solution accomplishes that goal elegantly and efficiently. 🎯
---
# Commit messages should explain why
URL: https://aleromano.com/posts/git-commits-why
Date: 2024-01-22
## How to write bad commit messages
Commit messages are more than just a formality. They are a form of asynchronous communication with:
- Your future self
- Your colleagues
- Anyone who needs to maintain the code in the future
One of the most common mistakes is writing a commit message that doesn't explain why the change was made, but instead summarizes the changes.
But I don't need the summary, I can read that myself by looking at the `diff`!
I need to understand why that change was necessary and the context behind it.
## Some examples
```bash
git commit -m "fixed bug on auth" # all auth was broken?
git commit -m "added a new featured image" # who told us to use a featured image?
git commit -m "updated dependencies" # normal maintenance? Changed libraries? Security issue?
git commit -m "refactored blog post to use function component" # what was wrong with the previous implementation?
```
Now, see how it becomes immediately easier to answer the questions for each commit using a different message:
```bash
git commit -m "sign-in with google users have no password to be stored"
git commit -m "caputre users focus by mixing images and text"
git commit -m "fix a security vulnerability"
git commit -m "improves rendering performance by leveraging referential transparency"
```
And the `diff` of each commit will tell me _how_ it was implemented.
Plus, the more attentive readers will have noticed another pattern...
## Don't use past tense
Using past tense verbs is a legacy from centralized versioning systems like Subversion and CVS. The past tense communicates a fact that has already been applied to the central repository.
But in a distributed system like Git, each commit is a point in time that may or may not be applied.
Write commit messages in the **present** tense or, even better, using the **imperative**.
It makes it much easier to follow the code history and makes it more natural to understand what `cherry-pick` and `revert` will do.
Here's related examples:
```bash
# Past tense commits
$ git log --oneline
abc1234 updated dependencies
def5678 added new featured image
$ git revert abc1234
# Creates new commit:
# "Revert 'updated dependencies'"
# (Confusing: are we downgrading dependencies?)
$ git cherry-pick def5678
# Creates new commit:
# "added new featured image"
# (Weird: we're adding something that was already added?)
```
Now compare it with:
```bash
# Imperative/present tense commits
$ git log --oneline
abc1234 fix a security vulnerability
def5678 caputre users focus by mixing images and text
$ git revert abc1234
# Creates new commit:
# "Revert 'fix a security vulnerability'"
# (Clear: we're going back to the previous unsecure state)
$ git cherry-pick def5678
# Creates new commit:
# "caputre users focus by mixing images and text"
# (Clear: we're applying the technique of mixing images and text)
```
## (Optional) Conventional Commits
As stated on [their website](https://www.conventionalcommits.org/en/v1.0.0/):
> A specification for adding human and machine readable meaning to commit messages.
Some examples:
- `feat`: new feature
- `fix`: bug fix
- `docs`: documentation changes
- `style`: changes that don't affect code (spaces, formatting, etc.)
- `refactor`: code changes that neither fix bugs nor add features
- `test`: adding or modifying tests
- `chore`: build process or auxiliary tool changes
You can then set up a hook to validate commit messages based on these conventions.
They have the additional benefit of simplifying the generation of `changelog` and release notes.
From my point of view, they're a great idea, especially when **working in a team**: it's a way to give rules and prevent everyone from using their own "style".
And in its own small way, using `feat` or `fix` already communicates part of the _why_ behind the change.
## References
I always try to use this style, in fact you can find countless examples by looking at the [complete commit history of this project](https://github.com/aleromano92/aleromano.com/commits/main/)!
I want to highlight a specific example though: the moment when I [added a Github Actions](https://github.com/aleromano92/aleromano.com/commit/0743094e24e40de33eb52561fa18c24fec28bf05) to deploy the site to my Hetzner VPS.
I could have used a commit message like: `added CI/CD using Github Actions`, but instead I chose to use: `i want to deploy by just committing`.
See? Just by reading the commit history, you understand that from this point on I automated the deployment. The how is easy to understand by looking at the `diff`:

## Conclusion
I think the right way to close this post is with an adapted quote from [Martin Fowler](https://www.martinfowler.com/):
> "Any fool can write commit messages that tell what changed. Good programmers write commit messages that explain why."
---
# YAGNI for Business
URL: https://aleromano.com/posts/yagni-for-business
Date: 2023-05-31
## Is YAGNI related to Waterfall and Agile? 🌊🏃🏻
> ⚠️ I won't advocate for which approach is better, I strongly believe every company should apply a process that suits its nature.
When developing a new product, it's essential to prioritize the features that customers need. This means that companies should develop only the features that are necessary to meet their customers' needs. By doing so, companies can save time and resources in the development process.
The **YAGNI** principle can also be applied to business processes. Companies should avoid implementing processes that are not necessary. This means that companies should only implement processes that are essential to their business operations. By doing so, companies can save time and resources and focus on the processes that are essential to their business operations.
A **waterfall** approach usually works on the opposite: first, you need to gather ALL the requirements, use cases and corner cases and design (or define) a process before anything is implemented.
## Example
Consider you want to build a form to collect phone numbers for an alerting system and there is a Pay-per-use billing behind every notification.
There are things you should define from the beginning, like making sure you are compliant with GDPR in EU countries and that you have a monitoring system to justify the invoices. But instead of focusing on all those things in the beginning, you can choose an MVP where:
- the country is not in the EU so GDPR does not apply (reduce _compliance activities_)
- the country is not so big, so you won't have billion of records from day one (reduce _technology constraints_)
- the country has a simple fiscal system to apply VAT on invoices (reduce _processes burden_)
You launch the form in the chosen country in a relatively small time and monitor how it is going: it may happen that the revenues you obtain from this MVP are already aligned to your plan.
This is **Agile**, this is **Lean**, you ain't gonna need GDPR compliance nor increase your Disk Storage nor study complicated fiscal systems. If you have planned from them since the beginning, _the market had already changed_ and you would still be stuck building the first release.
## The Dark Side of YAGNI
There are **risks** to using YAGNI without asking yourself questions. First, there are [2 types of decisions you can take](https://www.businessinsider.com/jeff-bezos-on-type-1-and-type-2-decisions-2016-4?r=US&IR=T):
1. Type 1 decisions, the not reversible one
2. Type 2 decisions, from where you can always go back
On Type 1 problems, not thinking about the future and the consequences may make you incur sunk costs later on. So, while you may still not define and plan anything in advance, you must stress your decision by asking yourself things like:
- what would I do if I later need to support GDPR-relevant countries?
- what if the volume hits so hard that our storage crashes? or our storage costs increase exponentially?
- how would I make sure the feature I build supports a country where the percentage of VAT is higher than the country I'm launching in?
While you are not **solving** these problems right now, you need to think of a way of doing them later and be **aware** on the risks and the shortcuts you are taking.
## Conclusion
> Prepare for the future, build the present.
---
# The Emotional Component of a Software Release
URL: https://aleromano.com/posts/emotional-component-software-release
Date: 2023-03-05
## Creating a Positive and Supportive Environment 🧘
The process of releasing software can be stressful and chaotic, especially when deadlines are tight and there's a lot riding on the success of the project. As the leader of the team, it's important to create a positive and supportive environment where everyone feels heard and valued. This means actively listening to the concerns and ideas of team members and working together to find solutions.
## Communication 📢
One key aspect of the emotional component of a software release is communication. Clear and effective communication is essential to the success of any project, and it's especially important during a software release.
During a software release, communication is critical in ensuring that everyone is on the same page and that potential issues are identified and addressed in a timely manner. This includes regular status updates, progress reports, and open lines of communication between team members, stakeholders, and any external partners involved in the project.
## Managing Expectations 🤙
Another important aspect of the emotional component of a software release is managing expectations. It's important to set realistic expectations and goals for the project and to communicate them clearly to everyone involved. This can help to prevent misunderstandings and reduce the likelihood of disappointment or frustration among team members and stakeholders.
At the same time, it's important to be flexible and adaptable in the face of unexpected challenges or setbacks. This means being willing to adjust timelines or priorities as needed, and being transparent about any changes or delays that may occur.
## Celebrating Success 🥳
Finally, it's important to take time to celebrate the successes and accomplishments of the team during and after a software release. This can help to boost morale and reinforce a positive and supportive team culture. Whether it's a small gesture like ordering lunch for the team or a larger celebration like a team outing, taking time to recognize and appreciate the hard work and dedication of the team can go a long way in fostering a positive emotional environment.
## Conclusion 🧑🤝🧑
The emotional component of a software release is an often-overlooked but critical aspect of any project. By creating a positive and supportive environment, communicating effectively, managing expectations, and celebrating success, technical leaders can help to ensure a successful and emotionally rewarding software release experience for all team members and stakeholders involved.
---
# One-to-One Coaching: 3 Reasons Why You Need It!
URL: https://aleromano.com/posts/one-to-one-coaching
Date: 2022-09-12
## Coaching Helps You Develop a Safe Continuous Feedback Culture ♾️
If the culture of your company is not conducive to open communication, then you could be missing out on one of the biggest benefits of a coaching relationship—namely, more frequent and consistent opportunities to give and receive feedback.
Employees would prefer to receive critical **feedback in person** rather than via an impersonal email. The sort of healthy, two-way communication that occurs during one-to-ones is likely to be far more conducive to **employee retention** than the sort of hierarchical communication that often happens over email.
Being able to give and receive feedback on the job is important since all employees make mistakes. And yet a lot of workers said they have never received any critical feedback at work, or worse, they said they fear receiving feedback so much, that they have avoided asking for it altogether.
By getting coaching, you can ensure that feedback is happening in a safe, constructive way that is beneficial for both the person giving it and the person receiving it.
## Coaching Helps You Foster Employee Growth 🚀
As a coach, you can provide training, support and guidance to employees who may be struggling with their careers.
The more closely you can observe and understand the employee’s needs and goals, the better you can help them achieve their objectives. You can also help employees gain confidence in themselves as well. The more confident an employee feels about his or her work history, the more likely he or she is to feel like he or she is making progress and becoming a better person.
A coach will also be able to provide helpful **feedback that helps employees build on their strengths and grow as individuals**. These feedback sessions should include providing advice on how to improve a particular skill (e.g., communication, refactoring, testing) or identifying areas to develop to be promoted (e.g., understanding budget concepts).
## Coaching Helps You Improve your Emotional Intelligence 🧘
Having one-to-one sessions is extremely beneficial for the coachee, but they have a high return on the coach as well.
Coaching requires you to have deep **emotional awareness**, be attentive to others, ask questions and actively listen, and have curiosity about others. All of these skills go towards improving your emotional intelligence. The more you put these skills into use, the stronger they get, and the better your coaching gets too.
You’ll get better at reading people, handling sticky situations, and helping others. You’ll become more confident in your abilities and feel more comfortable working with others.
You must be patient and have a desire to help others. You’ll also have to have some knowledge about the topic you’re coaching.
## Conclusion 🧑🤝🧑
During these meetings, you will come across many different personalities, each with their quirks and ways of thinking. By actively listening to your coachee and asking the right questions, you will be able to improve your emotional intelligence which leads to a **more meaningful career**.
---
# Upgrade Azure Schema Registry Without Downtime
URL: https://aleromano.com/posts/azure-schema-registry-upgrade
Date: 2022-08-31
## CONTEXT 🗒️
We use **Azure Schema Registry** so apps that need to produce and consume relevant events agree on an AVRO Schema. Doing so guarantees you won't fail on an unexpected property name or a nullable field you thought was mandatory.
> If the producer is able to emit an event based on a Schema of the registry, the consumer will read it correctly.
We started using the `Standard` version not knowing there was a hard limit on the number of AVRO schemas versions you can have for Schema Group: 25. And you can have only 1 Schema Group. `Standard` costs 25 euro/month, `Premium` 900 euro/month.
But the problem here is we didn't decide to spare some money: we didn't read carefully the limit of the `Standard` plan nor did we study how hard the upgrade is. _SPOILER_: you cannot do a hot upgrade, you must create a new `Premium` Schema Registry.
We created a Producer and 2 Consumers before hitting the limit
## TRY & FALLBACK 🦾
We started from the consumers so we could upgrade the producer on its own and at different times.
It is crucial not to temporally bind the deployment of producer and consumers: should you roll back one of them, you are forced to roll back the 3 of them 😐
We modified the consumers so they try to deserialize the event from Schema Registry Premium first. If it fails, we fall back to the Standard one. If the Standard fails too, it's a rightful error ✅
Here's the diff:
```diff
-import { ClientSecretCredential } from '@azure/identity';
+import { ClientSecretCredential, DefaultAzureCredential } from '@azure/identity';
import { SchemaRegistryClient } from '@azure/schema-registry';
import { SchemaRegistryAvroSerializer } from '@azure/schema-registry-avro';
import { Context } from "@azure/functions"
// -----------------------------------
+ const premiumClient = new SchemaRegistryClient(
+ process.env.AVRO_SCHEMA_REGISTRY_PREMIUM_FQDN,
+ new DefaultAzureCredential()
+ );
const serializer = new SchemaRegistryAvroSerializer(client, { groupName: 'ALL_AVRO_SCHEMA' });
context.log('Schema Registry Serializer obtained.');
+ const premiumSerializer = new SchemaRegistryAvroSerializer(premiumClient, { groupName: 'ALL_AVRO_SCHEMA' });
+ context.log('Schema Registry Premium Serializer obtained.');
+ let received = null;
- const received = await serializer.deserialize(serviceBusMessage);
+ try {
+ received = await premiumSerializer.deserialize(serviceBusMessage);
+ } catch {
+ context.log('Cannot deserialize from Premium Schema Registry, fallbacking to Standard...');
+ received = await serializer.deserialize(serviceBusMessage);
+ }
context.log('AVRO Message deserialized.');
```
> _NOTE:_ Using `DefaultAzureCredential()` allows us to use a `Managed Identity` to handle authorization [**better than using a client secret**](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview).
The test consisted in just deploying it in Test and observing it always enters the fallback branch.
We were able to update the Producer after 2 weeks: as soon as no other message was serialized with the `Standard` Registry, we removed the try&fallback code from the consumers ✂️
## SUCCEED 🤘🏻
- try to limit updates' scope and not have too many systems depending on each other ⛓️
- always try the upgrade in a Test environment 🦺
- when you plan on using a new Cloud Service, make sure you know the limit of the chosen plan and how hard is to upgrade 🤓
- discuss your migration strategy with a colleague or someone you respect so you may catch some flaw in your logic 🧑🤝🧑
---
# Mindfulness for Developers: Clean Code with a Clean Mind
URL: https://aleromano.com/posts/mindfulness-for-developers
Date: 2022-08-27
## How to practice meditation 🧘♀️
It's easy to start doing mindfulness meditation. All you have to do is sit down, close your eyes, and focus on the breath as it passes through your nostrils. You don't need any special tools or props—you can use a chair if you want, but it isn't necessary. You can even practice it while walking or eating.
If you're new to meditation, this might be intimidating at first: that's why I suggest you start with **guided** meditation using [**Headspace**](https://www.headspace.com/)
## Code can't be clean if your mind is not ☀️
Don't think of mindfulness as a state of mind. It's about being aware of what is happening in your mind and body. If you're not mindful, then you might be distracted by external factors and we all know how a long-lasting focus is fundamental when writing code.
Having a well-defined plan on what to do will help you write code that communicates better its **intentions**. Living in the present will prevent you from engineering unnecessary upfront abstractions.
Writing a test list before you start writing code is another technique that allows you to focus on one thing at a time and then move on to the next.
## Breath slowly and fix bugs 🐞
Breathing exercises are a great way to relax and calm down. They help you focus on what you are doing, which is essential when it comes to fixing bugs.
Sometimes we get so stubborn in trying to fix a bug we forget we should slow down, pause from hitting the keyboard and **re-think the problem from a different perspective**.
Next time you are debugging something without result or facing an incident, try to have 10-minutes of breathing: you will create space in your mind to come up with alternative routes to solve the issue.
## Be the perfect team member by giving to others 🧑🤝🧑
Be generous with your time and resources when it comes to teaching people who are new at something as complex as software development.
**Coaching** is useful for three reasons:
1. you verify your real knowledge regarding a topic and feel any gap you may have
2. you feel better helping others and knowing you left a trace in them
3. you get a sense of respect from the coachee and she/he will be more open when it will be your turn in getting help
## Don't be overwhelmed by your code reviews ⏸️
Doing and receiving code reviews is no easy task.
When reading all the comments, don't be defensive about any mistake or issue pointed out in your codebase. Encourage reviewers to explain their comments completely so you can better understand their points of view.
> Remember: they are highlighting issue on what they understood of your code, not on you.
At the same time, when reading someone else code, try to be as objective as possible and always assume good intentions. If you think they wrote inefficient solutions, try first to think why that may be the reason: were they in a rush? Didn't they know about that syntactic sugar that can improve the code? Didn't they know about the effects of this function on other systems?
> Understand your emotions and when they are influencing your judgement.
## Develop a habit of mindful coding ♾️
As with any other techniques in Software Development, like the _art of refactoring_ or Test-Driven Development, you must practice getting the most out of Mindful Coding.
Starting with a 10-minute breathing exercise after 1 hour spent debugging something without results can be a nice way to introduce yourself to this new experience.
Writing the test list only after a guided meditation can be another useful association: your brain will love it!
Connect your mind before plugging in your keyboard and enjoy a focused developer experience.
---
# My Morning Routine
URL: https://aleromano.com/posts/morning-routine
Date: 2022-07-18
## ⏰ Waking up (6.07 AM)
A fixed, consistent time helps your circadian rhythm and thanks to [**Fitbit Smart Wake**](https://www.fitbit.com/global/uk/technology/sleep) I can wake up between 5:37 and 6:07, according to my REM/Light/Deep sleep phases.
I keep the **same times on weekends** too.
## 🚰 Hydration (6.10 AM)
First things first: I get hydrated (with a little boost). During sleep, you naturally lose your liquids through sweating, so it's important to re-introduce them back: you'll feel more **awake** and without brain fog.
I used to drink just a glass of water in the cold seasons and one with some magnesium/potassium supplement during the hot ones. Now I started mixing my water with a scoop of [**SuperGreen Tonik**](https://supergreentonik.com/). I'm still evaluating if I'm getting any real improvement, a review will follow after I complete a 3-months cycle.
## 🧘♂️ Meditation (6.13 AM)
10 minutes are dedicated to mindfulness-guided meditation. Noting, body scan, practicing gratitude, breathing... Whatever Fitbit Premium inspires me for that particular day.
I used Headspace in the past, but now I'm focused on getting the best from a Fitbit Premium subscription.
Meditating in the morning allows me to create space in my mind so that:
- I remember how **grateful** and lucky I am for my life and family
- I can address my emotions during work not being **overwhelmed**
- I get to **know myself** better every day
## 🏋️♂️ Workout (06.27 AM)
Just 10-15 minutes. Seriously.
If you believe 10-minutes workouts are useless... Well, you think exactly like me before I tried! And you (and me) are wrong.
I'm not saying I'll have a super-defined body, lose weight or become a real athlete, but this is **not my purpose**. A 10-minutes workout is enough to:
- improve my **mood** by releasing dopamine and serotonin, as I feel satisfied when I finish and I don't feel guilty not having sweat at all
- relax my muscles' physical tension caused by working with 2 monitors and staying sit all-day
- keep the **habit** and help me achieve 1 hour of exercise per week
Of course, when I have more time like during holidays or weekends I try to do some other aerobic activities like walking and cycling. I miss the time I played tennis, but I'm still struggling to find the time to play while balancing work and family.
## 🥐 Breakfast (6.42 AM)
Time to reward me for being consistent: I drink a coffee with a snack, sometimes a glass of fruit juice too. I'm not yet having a super sane breakfast with fruits and vegetables, it may be something to add later in the year.
## 🃏 Free time while everyone is still asleep
Now is when my routine begins to be less fixed. I do what I need the most.
I may cook for me and wife so we have a lunchbox, read some newsletter, search for something, read a book, write this blog, etc
It's like a 20-minute wildcard I can choose to play on the most needed activity.
## 👨🍼 Caring for Mattia Dante (7.07 AM)
Mattia (Dante is his second name) is my 1-year baby.
I wake him up, feed him with milk, change the diaper, give a D3 vitamin supplement and dress him up so he's ready to be "delivered" to kindergarten by my wife Giulia.
It's not only a duty, I enjoy these moments as he's usually happy and fun and make me and Giulia's day start with a **lot of laughs**.
## 🃏 Free time while I'm alone (7.47 AM)
I usually get another 30-40 minutes to do other things I need or want to do. As I'm alone and no one is sleeping, I can even **play some music**.
If I'm not working from home, this is the time I catch my train.
## 💻 Start working (8.25 AM 8.40 AM)
My work day starts when no one else is already working so I can prepare for my schedule and check emails.
I won't describe my work day in this article.
## 🛏️ Going to bed (11.00 PM)
This is not part of the morning, but it is fundamental to keep my routine.
I'm able to get 7 hours of sleep this way, which is not as good as 8 hours but is enough for me in this moment of my life. I may have 30 minutes naps during the weekend.
I try to stop using any electronic devices after 10.30 PM, the only exception being my Kindle Paperwhite. Reading is the thing I do to relax before falling to sleep.
---
# I renovated my Home Office
URL: https://aleromano.com/posts/home-office-renovation
Date: 2022-03-07
> Disclaimer: you will find Amazon affiliate link in this post. If you buy from it, you will offer me a beer, more or less, with no additional cost to you 👍
I decided to renovate my Home Office.
The 4 traits I focused on:
1. increase comfort 💺
2. improve sound experience 🔊
3. look better during video calls 💅🏻
4. reduce distractions ❗
## This was the starting point

A horizontal desk 160x60cm, an external monitor, keyboard and mouse. In this picture, you can see my old Surface 3 (I sold it) and my books and notebooks while studying for my degree.
## The cons
- The chair did not have lumbar support.
- I could not lay my arms on the desk while typing, because the desk was not large enough: the keyboard was too close to the desk border.
- The keyboard was an old (but good) apple Keyboard with a Mighty Mouse.
- 🔊 I had no external speakers nor microphone, so my day was always wearing in-ear headphones (a Sony pair which I really love [**amzn.to/3pH7TwQ**](https://amzn.to/3pH7TwQ) ).
- 💅🏻 I did have a lot of shadows and light coming from the left, making me not look good on the webcam.
- ❗The room was shared with my wife: her desk was on the opposite side of the room. but we had problems when we both attended meetings. Plus, we used that room as a closet so it was a bit messy.
## Start renovating
We have a very spacious house (lucky us) so I was able to use part of our basement to create a private Home Office. The room is 3.80 x 2.80 mt and I started sketching a possible furniture disposition.

The most important part I focused on was the desk. I wanted a big corner desk with plenty of room to lay down my arms (45cm from desk border to keyboard) and the right distance to my monitors (60-70cm ready to migrate to a big curved one).
I wanted a library where I can show my Final Fantasy, Kingdom hearts and One Piece collections: manga, video games and action figures so I can turn off any background filter during video calls and anyone can get a glimpse of my passions. 🏯
I wanted to create an atmosphere where the white, wooden and green colors can mix and match to give me a relaxed, bright environment.
So I bought some SPC floor to easily install on top of the existing one: their click mechanism allowed me and my wife to do it on our own.

I looked on different second-hand sites and found my desk, wardrobe and library! There was a lot of fun renting a van with my wife and going to get all of them 🚚

## New purchases
First of all, I bought a gaming chair. After countless reviews and going through stores, I found the best for my needs in a GT Player chair: [**amzn.to/34mU53m**](https://amzn.to/34mU53m)
Then, I wanted a portable mechanical keyboard with a numeric pad. I always spied on Keychron and in the end I bought a K4: [**amzn.to/3sWJVQB**](https://amzn.to/3sWJVQB)
I wanted a new mouse too, an ergonomic wireless one like the Anker I bought: [**amzn.to/3vL5nK6**](https://amzn.to/3vL5nK6)
I bought a transparent carpet to put under the chair in order to protect the new floor from the chair wheels: [**amzn.to/3tCu0pC**](https://amzn.to/3tCu0pC)
I already have a desk lamp I use to enlighten my face during video calls, here is the link: [**amzn.to/3vJx7P7**](https://amzn.to/3vJx7P7)
I bought 3 small plants: aloe vera, Ficus Benjamin and Lucky bamboo. They should help keep the air quality good.

Regarding lightning: for offices, it is advised to have 300-400 Lux per square meter. Simple math 400x11 = 4400 Lumen needed. I bought a lamp holder
[**https://amzn.to/3Cm01Gn**](https://amzn.to/3Cm01Gn)
with 6 LED bulbs 800 Lumen each
[**amzn.to/3IKPCX4**](https://amzn.to/3IKPCX4)
I chose a warm light as I find it more relaxing.

This is a bit of what the office looks like right now 😁


## Still missing
1. a microphone that makes me sound like a rock star 🧑🏻🎤🎙️
2. external speakers that make my in-ear headphones not needed at all 🎶
3. a fixed external webcam with higher definition 📸
4. sound absorbing panels
5. a big wide curved monitor (I think 1 big monitor may be superior to the 2 monitors setup)
6. probably, an air conditioner as I think summer may be really hot
7. a beautiful organizer like [**amzn.to/372JWcV**](https://amzn.to/372JWcV)
8. some paintings
---
# Effective meetings start from the agenda
URL: https://aleromano.com/posts/effective-meetings-agenda
Date: 2022-01-16
Have you ever found yourself stuck in a meeting where you didn't know:
- What your contribution should be
- What was the objective
- What they were talking about
Or even worse, if the meeting ended up _with no results_… **This is the post you were looking for!** Your entire productivity and success depend on fixing issues like this, too. How? Here you find some small (but **effective**) tricks.
Every meeting should have an agenda (this sentence can finish here and it would still be a piece of _precious advice_) with 3 points: context, objective and notes.
## **CONTEXT 🗒️**
You should describe in a few words why this meeting has been set. What's the project or the initiative, what were the decisions already made and what is the main topic. In this way, all the attendants can prepare in advance and more important know if they are relevant to the discussion.
Let's make some **good** examples:
- BigCorp would like to launch a new insurance product within June. The end user contract has already been defined.
- We are adapting our systems and processes according to the new law: we have already fixed products 1 and 2, and we are now focused on product 3.
And some **bad** examples:
- BigCorp is one of the greatest companies in Europe
- Let's talk about evolutions on our website
## **OBJECTIVE 🎯**
This section should complete the sentence "When the meeting ends, it would have been a success if...". Objectives (they may be more than one) should be simple and easily measurable.
**Good** objectives:
- Solve all the open points of the H column of the attached Excel
- Approve or deny the budget given the business case we are going to present
- Finalize the algorithm deciding on proposal A or B
**Not-so-good** objectives:
- Talk about the new product
- Understand how to handle this process
- Review the excel
When you are not able to set clear objectives it may be a sign **it's still not time to call a meeting**, you may need more time working offline gathering doubts and things to discuss.
## **NOTES 📓**
This is an optional section where you may signal attention points, from logistics to temporal ones.
## **Other things to consider**
- if you need to invite creative people like developers, it is better not to set them in the middle of the day or afternoon interrupting a block of work
- if you spend most of the meeting time sharing a screen it's a good signal the meeting was well prepared and you are going straight to the objectives
- except for demos or brainstormings, the more people you add to a meeting the higher the risk of inefficiency or excluding part of the audience
- a recap mail at the end of the meeting is always welcomed, it is easier if objectives were well written
- you should not fear you may be seen as disrespectful in not accepting a meeting until context and objective are not clarified by the organizer
You need to practice.
So, what are you waiting for? Take a second look at the meetings you have already planned or go back to this post next time you need to set up one!
---
# From Mac OS X to Windows 10 as a Developer
URL: https://aleromano.com/posts/mac-to-windows
Date: 2020-11-01
## Ok, but... why?
I'm working for [**AideXa**](https://www.aidexa.it/) as Head of Engineering since October after 4 beautiful years at [**lastminute.com**](https://lastminute.com/). As Software Engineer I was so used to my Macbook Pro that I thought I would never be able to go back. AideXa, as a company acting in a High Regulated Environment, has strong requirements in terms of devices and control over them, that's why we preferred to stick to a Microsoft-driven ecosystem with Azure, Active Directory and the Office suite. This doesn't mean we are not evaluating introducing any Apple notebook in our company policy should they prove superior in particular areas (yeah, M1 CPU I'm talking about you).
## Windows has changed
It is crystal clear Microsoft has changed its mind regarding Open Source and providing better experiences for Developers. One of the key features I use the most is **Windows Subsystem for Linux** (WSL). They even released [**Visual Studio Code**](https://code.visualstudio.com/) which is a pearl in the world of Code Editors (with superpowers).
## Spotlight -> Win key
This is a quick Win (pun intended) as Windows Search is greatly improved: press down it, type a query and you'll get a smart search across apps, files and settings.
## Hot Corner to logout -> Win Key + L
I used to move my cursor in the bottom right corner to securely block my Mac. I now press Win Key + L.
## Terminal/iTerm -> Windows Terminal
Another great example of Microsoft embracing open source, releasing its applications and making them suitable for developers. [**Windows Terminal, Console and Command line**](https://github.com/microsoft/terminal) has all the features I needed: it works well with the old **cmd**, **Power Shell** and **bash** console you get by installing WSL.
## Maccy/Clip -> Win Key + V (Clipboard Manager)
I think the feature I abuse the most. You don't need any external application, it is built-in in any Windows 10 setup. Just hold Win Key + V to get the Clipboard Manager that (once activated) can store almost infinite Entries, including text, images and whatever you were able to . It is a breeze to remove all or just some of them and you can even pin your favorite!

## Screenshot -> Win Key + Shift + S
On Mac OS you can press Command + Shift + 4 to get the Snipping Tool and select the area you want to capture. You can add Control to your selection to save it in the clipboard.
On Windows, you can access the same feature by hitting Win Key + Shift + S. This is even more powerful as allows you to quickly edit the screenshot before saving it, like for example adding some red circles around important info.

## Emoji Picker -> Win Key + . (dot)
While on Mac OS you can press Command + Control + Spacebar to open the emoji picker

On Windows, you can access an even better version (it includes GIF search too) by hitting Win Key + . (dot)
