<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>The joy of programming</title>
    <link>https://joy.pm/</link>
    <description>A tiny scrapbook for ideas on software development, the world, life and everything else inbetween.</description>
    <lastBuildDate>Tue, 10 Mar 2026 08:42:44 +0000</lastBuildDate>
    <atom:link href="https://joy.pm/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Highly customized websites</title>
      <link>https://joy.pm/highly-customized-websites/</link>
      <guid isPermaLink="true">https://joy.pm/highly-customized-websites/</guid>
      <pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>For years I ran this blog on Ghost. It is a fine blogging platform —
polished, fast, and it mostly stays out of your way. But it is also a
service you depend on, a Node.js process you have to keep alive, a
database to back up, and a monthly bill to justify.</p>
<p>A personal blog is, at its core, a handful of Markdown files and some
HTML templates. Throw a bit of pandoc in the mix and you have a full
fledged website.</p>
<p>I always wanted to write my own small blog engine. I want tiny
features that no other blog engines have. For example why not adding a
small game you can play:</p>
<div style="display:flex;flex-direction:column;align-items:center;margin:2rem 0;">
  <canvas id="arkanoid" width="480" height="320" style="border:1px solid #51afef;background:#1c1f24;display:block;"></canvas>
  <p style="font-size:0.8rem;color:#abb2bf;margin-top:0.5rem;">← → to move &nbsp;|&nbsp; click canvas to start</p>
</div>
<script>
(function() {
  const canvas = document.getElementById('arkanoid');
  const ctx = canvas.getContext('2d');
  const W = canvas.width, H = canvas.height;

  const COLS = 10, ROWS = 5;
  const BW = 42, BH = 14, BPAD = 4;
  const BOFF_X = (W - COLS * (BW + BPAD) + BPAD) / 2;
  const BOFF_Y = 40;
  const COLORS = ['#ff6c6b','#da8548','#ecbe7b','#98be65','#51afef'];

  let bricks, ball, paddle, lives, score, state;

  function init() {
    bricks = [];
    for (let r = 0; r < ROWS; r++)
      for (let c = 0; c < COLS; c++)
        bricks.push({ x: BOFF_X + c*(BW+BPAD), y: BOFF_Y + r*(BH+BPAD), alive: true, color: COLORS[r] });

    paddle = { x: W/2 - 40, y: H - 24, w: 80, h: 12 };
    ball   = { x: W/2, y: H - 60, vx: 3, vy: -3, r: 7 };
    lives  = 3; score = 0; state = 'idle';
  }

  const keys = {};
  document.addEventListener('keydown', e => { keys[e.key] = true; });
  document.addEventListener('keyup',   e => { keys[e.key] = false; });
  canvas.addEventListener('click', () => { if (state === 'idle' || state === 'dead') state = 'running'; });

  function update() {
    if (state !== 'running') return;

    // Paddle
    const spd = 5;
    if (keys['ArrowLeft'])  paddle.x = Math.max(0, paddle.x - spd);
    if (keys['ArrowRight']) paddle.x = Math.min(W - paddle.w, paddle.x + spd);

    // Ball
    ball.x += ball.vx; ball.y += ball.vy;

    if (ball.x - ball.r < 0)  { ball.x = ball.r;      ball.vx *= -1; }
    if (ball.x + ball.r > W)  { ball.x = W - ball.r;  ball.vx *= -1; }
    if (ball.y - ball.r < 0)  { ball.y = ball.r;       ball.vy *= -1; }

    // Ball hits paddle
    if (ball.vy > 0 &&
        ball.y + ball.r >= paddle.y &&
        ball.x >= paddle.x && ball.x <= paddle.x + paddle.w) {
      ball.vy *= -1;
      ball.vx = ((ball.x - (paddle.x + paddle.w/2)) / (paddle.w/2)) * 4;
      ball.y = paddle.y - ball.r;
    }

    // Ball out of bottom
    if (ball.y - ball.r > H) {
      lives--;
      if (lives <= 0) { state = 'over'; return; }
      ball.x = W/2; ball.y = H - 60; ball.vx = 3; ball.vy = -3;
      state = 'dead';
    }

    // Bricks
    for (const b of bricks) {
      if (!b.alive) continue;
      if (ball.x + ball.r > b.x && ball.x - ball.r < b.x + BW &&
          ball.y + ball.r > b.y && ball.y - ball.r < b.y + BH) {
        b.alive = false; score += 10;
        const overlapL = ball.x + ball.r - b.x;
        const overlapR = b.x + BW - (ball.x - ball.r);
        const overlapT = ball.y + ball.r - b.y;
        const overlapB = b.y + BH - (ball.y - ball.r);
        const minH = Math.min(overlapL, overlapR);
        const minV = Math.min(overlapT, overlapB);
        if (minH < minV) ball.vx *= -1; else ball.vy *= -1;
        break;
      }
    }

    if (bricks.every(b => !b.alive)) state = 'won';
  }

  function draw() {
    ctx.clearRect(0, 0, W, H);

    // Bricks
    for (const b of bricks) {
      if (!b.alive) continue;
      ctx.fillStyle = b.color;
      ctx.fillRect(b.x, b.y, BW, BH);
    }

    // Paddle
    ctx.fillStyle = '#9AB8FA';
    ctx.beginPath();
    ctx.roundRect(paddle.x, paddle.y, paddle.w, paddle.h, 4);
    ctx.fill();

    // Ball
    ctx.fillStyle = '#ffb300';
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
    ctx.fill();

    // HUD
    ctx.fillStyle = '#abb2bf';
    ctx.font = '13px "IBM Plex Mono", monospace';
    ctx.fillText(`score: ${score}`, 10, 22);
    ctx.fillText(`lives: ${'♥'.repeat(lives)}`, W - 90, 22);

    // Overlays
    if (state === 'idle') overlay('click to start');
    if (state === 'dead') overlay('click to continue');
    if (state === 'over') overlay('game over');
    if (state === 'won')  overlay('you win!! 🎉');
  }

  function overlay(msg) {
    ctx.fillStyle = 'rgba(28,31,36,0.75)';
    ctx.fillRect(0, H/2 - 28, W, 48);
    ctx.fillStyle = '#51afef';
    ctx.font = 'bold 20px "IBM Plex Mono", monospace';
    ctx.textAlign = 'center';
    ctx.fillText(msg, W/2, H/2 + 8);
    ctx.textAlign = 'left';
  }

  init();

  function loop() { update(); draw(); requestAnimationFrame(loop); }
  loop();
})();
</script>

<p>This is a different kind of software. Not a product you maintain
forever, but a small tool that does exactly what you need today and can
be reshaped tomorrow at will with a bunch of prompts. You don’t have a
product per se but a lot of markdown files being duct taped by open
source projects. All managed with a coding agent acting as your obedient
assistant.</p>
<h2 id="what-i-ended-up-with">What I ended up with</h2>
<p>This site is now generated by a ~500-line shitty Python script I
wrote with the help of Claude Code. It reads posts from a
<code>posts/</code> directory, runs them through Pandoc, renders Jinja2
templates, and writes static HTML to a <code>dist/</code> folder that
Cloudflare Pages serves directly.</p>
<p>Deployment is a git push. As it should be.</p>
<p>I must admit I’m using AI to detect which tags a post should have. I
don’t think it’ll work perfectly, but the cost of changing it is small.
And the icing on the cake wasn’t just that. It’s that I can use coding
agents to actually interface with the blog’s code, so I can customize
the UI to my own needs.</p>
<p>I also used AI to migrate the old posts to plain markdown stored in a
git repo. The CSS migration is not perfect, so some old posts will still
render like shit, but I have plenty of time to fix them.</p>
<h2 id="harnesses-are-not-only-for-code">Harnesses are not only for
code</h2>
<p>Something I want to experiment with is the use of <a
href="https://github.com/badlogic/pi-mono">pi</a> as an assistant on
working in the blog. You can reshape pi to do whatever you want. In the
blog repo, there is a small pi UI component that shows how many posts
and drafts I have. I use VS Code to write and pi to proofread, correct
typos, handle assets, and change the build…</p>
<h2 id="everything-is-just-text">Everything is just text</h2>
<p>At the end of the day, if you have a sufficiently powerful harness
you can shape your output to your will. It is a weird feeling. I am used
to the safety of deterministic software (or at least the illusion of
it), and embracing this “lack of knowing everything” is a bit
worrisome.</p>
<figure>
<img src="assets/stats-widget.png" width="200"
alt="The stats widget in pi" />
<figcaption aria-hidden="true">The stats widget in pi</figcaption>
</figure>
<p>Anyway, the future is bright. I can add a small game to a blog post
just to prove a point. The future is surely weird.</p>
<p>Have fun!!</p>
]]></description>
    </item>
    <item>
      <title>Context is part of the game</title>
      <link>https://joy.pm/context-is-part-of-the-code/</link>
      <guid isPermaLink="true">https://joy.pm/context-is-part-of-the-code/</guid>
      <pubDate>Sun, 08 Feb 2026 10:15:43 +0000</pubDate>
      <description><![CDATA[<p>For a while I used coding agents for small, self-contained things.
Refactor this method. Rename this variable. Extract this into a
function. Safe, short instructions where the agent couldn't go too far
off track.</p>
<p>But I kept seeing people push further — giving agents a longer leash,
taming them into handling real features end to end. And it was working.
The gap between what I was doing and what seemed possible was
growing.</p>
<p>So I started trying. And the results were mixed. Sometimes the agent
would nail it. Other times it would go in circles, making
reasonable-looking choices that were completely wrong for the project.
Wrong abstraction. Wrong assumptions about the data model.</p>
<p>The difference, I've come to believe, is context. Not prompt
engineering tricks, not model selection. Context — how much of it you
provide, and how you provide it.</p>
<h2 id="the-colleague-problem">The colleague problem</h2>
<p>When I sit down with a colleague to discuss a feature, we share a
tremendous amount of context without even thinking about it. We both
know the codebase. We both know the users. We both know the last three
decisions that shaped the architecture. We both know which parts of the
system are fragile and which are rock solid.</p>
<p>An agent knows none of this. It starts every conversation from zero.
And yet I routinely catch myself typing something like "this thing is
happening and it shouldn't" and expecting the agent to make the same
choices I would.</p>
<p>It won't. It can't. Not because it's incapable, but because it
doesn't have the information it needs.</p>
<h2 id="what-actually-helps">What actually helps</h2>
<p>I've started paying attention to what makes a prompt produce good
results versus mediocre ones. It's not about writing more. It's about
including the right things:</p>
<ul>
<li><strong>What</strong> I'm building and <strong>why</strong>. The why
matters more than I expected. "Add a login page" and "add a login page
because we need to gate access to paid features and our users are
non-technical" lead to very different implementations.</li>
<li><strong>Constraints</strong>. What libraries am I already using?
What patterns does the codebase follow? What can't change? Normally I
provide those in the form of documentation od the project.</li>
<li><strong>Decisions already made</strong>. If I've already decided on
JWT over sessions, I say so. Otherwise the agent might pick the other
one and I'll waste twenty minutes unwinding it.</li>
<li><strong>What I'm unsure about</strong>. This is the one I used to
skip. But telling the agent where my thinking is still fuzzy turned out
to be incredibly valuable. It fills gaps I didn't know I had.</li>
<li><strong>How to know when it's done</strong>. This one changed
everything for me. If you can tell the agent "run this test suite" or
"check that the API returns 200 on this endpoint," you've closed the
loop. The agent can try something, verify it, and try again if it didn't
work. Without a definition of done, it generates code and hopes. With
one, it iterates.</li>
</ul>
<figure class="kg-card kg-image-card">
<img src="./assets/photo_2026-02-08-11.13.13.jpeg" class="kg-image" loading="lazy" width="1376" height="768" />
</figure>
<p>And that last point is where things start to feel different. When the
agent can check its own work, it stops feeling like a text generator and
starts feeling like a harness — like you're constraining the execution
of a wild creature that can produce thousands of lines of code per
second, and your job is to point it in the right direction and let it
run.</p>
<h2 id="the-agent-as-a-thinking-partner">The agent as a thinking
partner</h2>
<p>The shift that changed my workflow the most was to stop treating the
agent as a code generator and start treating it as a thinking
partner.</p>
<p>I used to try to write a complete spec before handing anything to the
agent. That felt responsible. But it was often slower and worse than
writing a rough one and refining it together. The agent spots gaps in my
thinking. It asks about edge cases I haven't considered. It surfaces
assumptions I didn't know I was making.</p>
<p>My workflow used to be: think hard, write complete spec, hand it to
the agent, get code back. Now it's more like: write down what I know,
let the agent challenge and expand it, arrive at a better spec together,
then build from that shared understanding.</p>
<p>The second approach produces better specs <em>and</em> better code,
because the agent understands the reasoning behind the decisions, not
just the decisions themselves.</p>
<h2 id="building-this-into-the-process">Building this into the
process</h2>
<p>I liked this idea enough that I built it into a reusable skill I call
<code>interview-spec</code>. You point it at a rough specification file,
and instead of immediately generating code, it interviews you.</p>
<p><a
href="https://gist.github.com/rafadc/dddff65825825bbfe7c12f39dfe45229">interview-spec
on GitHub Gist</a></p>
<p>First, it reads the spec and identifies what's there and, crucially,
what's missing. It looks for gaps in error handling, edge cases,
security considerations, performance implications, and operational
concerns.</p>
<p>Then it starts asking questions. Not obvious ones. It asks about race
conditions in the data model. About what happens when the network fails
halfway through a transaction. About how to roll this back if something
goes wrong in production. About which parts are truly MVP and which are
nice-to-haves that got unconsciously promoted to requirements.</p>
<p>It asks 2-4 questions at a time, drills deeper based on answers, and
periodically summarizes what it has learned to make sure we're on the
same page.</p>
<p>By the end, it updates the original spec with everything that was
uncovered and produces a prioritized task breakdown with complexity
estimates and dependencies.</p>
<p>The key insight for me was that this back-and-forth is not overhead.
It's the actual work. The conversation <em>is</em> the specification
process. Every question the agent asks is a question that would have
surfaced eventually — probably at a much more expensive time, like
during implementation or, worse, in production.</p>
<h2 id="making-context-persistent">Making context persistent</h2>
<p>This goes beyond individual conversations. I've started making
context available in places the agent can find on its own:</p>
<ul>
<li>A <code>AGENTS.md</code> (which I symlink to <code>CLAUDE.md</code>)
file in each repository root with project conventions, architecture
decisions, and patterns to follow.</li>
<li>Clear commit messages and PR descriptions. The agent reads those
too.</li>
<li>Documentation that captures <em>why</em> things are the way they
are. Not exhaustive docs — just the decisions that matter.</li>
</ul>
<p>Every piece of context I make available is a decision the agent
doesn't have to guess about.</p>
<h2 id="letting-the-agent-find-its-own-context">Letting the agent find
its own context</h2>
<p>There's another dimension to this that took me a while to appreciate:
you don't have to hand the agent all the context yourself. You can let
it go get it.</p>
<p>Tools are how an agent builds its own context. Every tool call is the
agent reaching out into the world to learn something it didn't know.
Reading a file, running a command, hitting an API — these aren't just
actions, they're the agent filling in its own gaps.</p>
<p>And the interesting part is that you don't always need formal tool
integrations for this. Sometimes just reminding the agent what's
available on the system is enough. If <code>gh</code> is installed and
configured, telling the agent it can use it means it can traverse a PR,
read its comments, check CI status, and piece together the full story
behind a bug — all without me having to copy-paste any of that into the
prompt.</p>
<p>I've started thinking about it this way: I provide the <em>why</em>
and the <em>constraints</em>, and I make sure the agent has the tools to
discover the <em>what</em> on its own. That split turns out to be a much
better use of everyone's time than trying to front-load every detail
myself.</p>
<h2 id="the-real-cost">The real cost</h2>
<p>Providing good context takes effort. It forces me to articulate
things that feel obvious and to confront the parts of my plan that are
still vague. I can't hide behind ambiguity anymore.</p>
<p>But that effort isn't wasted. It's the same effort I'd spend
debugging a misguided implementation, rewriting code that went in the
wrong direction, or wondering why the agent "did it wrong" when the real
problem was that I never said what "right" looked like.</p>
<p>Context is not a tax on using an agent. Context is the work. The
agent just makes that truth impossible to ignore.</p>
]]></description>
    </item>
    <item>
      <title>Making LLMs play old text based adventures</title>
      <link>https://joy.pm/making-llms-play-old-text-based-adventures/</link>
      <guid isPermaLink="true">https://joy.pm/making-llms-play-old-text-based-adventures/</guid>
      <pubDate>Sat, 10 Jan 2026 18:45:00 +0000</pubDate>
      <description><![CDATA[<p>There is a game from my youth on which I spent countless hours as a
small child with my Amstrad CPC 6128. I have very fond memories of
spending the full afternoon trying to move around in its map. If you are
thinking it is a super advanced 3D game with fast paced action you are
absolutely wrong.</p>
<p>The game is Don Quijote. A text based adventure.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image.png" class="kg-image" loading="lazy" width="500" height="331" />
</figure>
<p>Yes. Just some super naive graphics. A small REPL-like interface and
you are good to go to live amazing adventures.</p>
<p>Since we are in the world of AI one tiny fun experiment I fantasized
about is letting the AI play the game on its own to see how far it could
go.</p>
<p>Even if I loved that one it is not the optimal choice to use as a
learning point. I want something with better feedback on the status of
the game.</p>
<p>There is one oldie but goldie I can use for that.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image.png" class="kg-image" loading="lazy" width="250" height="298" />
</figure>
<p>Well, I can use many games in reality but is a good one because it
has a score. In the game you have to get some items, place them in the
shelves and you get points for that.</p>
<h2 id="experiment-1-the-naive-start">Experiment 1: The naive start</h2>
<p>Piece of cake. Just start your application, and have a python script
remote control that window typing the contents.</p>
<p>This is a tiny example so GPT-5-mini is a nice start.</p>
<p>Since we are talking about on screen text the starting point can be
just OCRing with something like tesseract (so it is faster) and send the
things to the llm outputting the response.</p>
<p>First gameplay? Surprisingly fine.</p>
<figure class="kg-card kg-embed-card">
<div class="iframe">
<div id="error-block">
<h1 id="error-title">
</h1>
<div id="error-content">

</div>
Error details
<div id="error-details-content">

</div>
</div>
<div id="video-password-block">
<h1 id="video-password-title">
</h1>
<div id="video-password-content">

</div>
<div id="video-password-error">

</div>
<p><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0cmVtIiBoZWlnaHQ9IjRyZW0iIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzRjNGM0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHJlY3Qgd2lkdGg9IjE4IiBoZWlnaHQ9IjExIiB4PSIzIiB5PSIxMSIgcng9IjIiIHJ5PSIyIiAvPjxwYXRoIGQ9Ik03IDExVjdhNSA1IDAgMCAxIDEwIDB2NCIgLz48L2c+CiAgICAgIDwvc3ZnPg==" /></p>
</div>
<div id="video-wrapper">

</div>
</div>
</figure>
<p>Ok, ok. I know going rogue on that troll was not the smartest thing.
But for a first iteration it is not bad. Now, unfortunately it is very
hard for the LLM to know that it can resurrect or even restart the game.
So let's add some small context.</p>
<h2 id="experiment-2-giving-more-context-to-the-agent">Experiment 2:
Giving more context to the agent</h2>
<p>The LLM, at this moment is treating each and every room individually
as it saw it afresh. In Zork there is this weird Loud Room that just
echoes everything you type. The agent gets crazy and tries to restart
the game. It is fun because that is one room where you cannot restart
the game. That makes it enter an infinite loop.</p>
<p>A naive solution for this is to just turn the dialog of the game in a
conversation with the agent with the agent storing all the messages in
the context.</p>
<p>For long sessions the context may rot so let's implement a sliding
window. I experimented a bit with the number of messages to keep. I
started with 50 or 100. With those numbers the game plays far better.
Anyway, there is not enough context to learn from more than one game.
You just avoid the last death in the best case scenario.</p>
<h2 id="experiment-3-giving-it-long-term-memory">Experiment 3: Giving it
long term memory</h2>
<p>Right now, the agent learns a bit from history but it would be great
if it had the ability to keep things like where objects are and what
happens if you try to swing your sword to that troll.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-1.png" class="kg-image" loading="lazy" width="571" height="372" />
</figure>
<p>That is the purpose of the long term memory. You distill the content
a bit more there to avoid having a lot of pollution and you also keep a
short term memory so recent events have some degree of preeminence.</p>
<p>In LangGraph we have the checkpointers that allow us to do this
easily.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> langgraph.graph <span class="im">import</span> StateGraph, START, END</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> langgraph.checkpoint.sqlite <span class="im">import</span> SqliteSaver</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> langchain_anthropic <span class="im">import</span> ChatAnthropic</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> typing <span class="im">import</span> TypedDict, Annotated</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> langgraph.graph.message <span class="im">import</span> add_messages</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> State(TypedDict):</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    messages: Annotated[<span class="bu">list</span>, add_messages]</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> chatbot(state: State):</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>    llm <span class="op">=</span> ChatAnthropic(model<span class="op">=</span><span class="st">&quot;claude-sonnet-4-20250514&quot;</span>)</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>    response <span class="op">=</span> llm.invoke(state[<span class="st">&quot;messages&quot;</span>])</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> {<span class="st">&quot;messages&quot;</span>: [response]}</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>graph <span class="op">=</span> StateGraph(State)</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>graph.add_node(<span class="st">&quot;chatbot&quot;</span>, chatbot)</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>graph.add_edge(START, <span class="st">&quot;chatbot&quot;</span>)</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>graph.add_edge(<span class="st">&quot;chatbot&quot;</span>, END)</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>checkpointer <span class="op">=</span> SqliteSaver.from_conn_string(<span class="st">&quot;chatbot_memory.db&quot;</span>)</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>app <span class="op">=</span> graph.<span class="bu">compile</span>(checkpointer<span class="op">=</span>checkpointer)</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>config <span class="op">=</span> {<span class="st">&quot;configurable&quot;</span>: {<span class="st">&quot;thread_id&quot;</span>: <span class="st">&quot;conversation-X&quot;</span>}}</span></code></pre></div>
<p>That will persist all the messages states in sqlite. It is not the
best way to do it but for now it will suffice.</p>
<p>In our case we will use the game as a thread_id. This way the
conversations with the agent won't be mixed between games.</p>
<p>But probably you will be thinking... we are doing the same thing we
were doing before, aren't we? And yes, you'd be absolutely right. With
this we just implemented short term memory. We need to store some kind
of long term memory too.</p>
<p>We will introduce the concept of a game_guide in the state of the
agent</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> AgentState(TypedDict):</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    messages: Annotated[List[BaseMessage], operator.add]</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    last_observation: <span class="bu">str</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    action: <span class="bu">str</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    game_guide: <span class="bu">str</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    steps: <span class="bu">int</span></span></code></pre></div>
<p>So the state of the agent now has a game_guide that is updated every
X steps. It is like the agent is writing one of those gamefaqs.com
guides in text and now that becomes our source of truth.</p>
<p>With this the game gets pretty far. Sometimes it still gets stuck.
Especially in areas where the map is special like the forest.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-2.png" class="kg-image" loading="lazy" width="1094" height="1212" />
</figure>
<p>Even if you have the history the model tends to take "impulsive"
decisions. If there is a mailbox tends to overspend attention on it
taking a look to it and overplaying with it when there is probably
nothing else to do.</p>
<p>This is a bit similar to what we do in Claude Code when we compact
the history.</p>
<h2 id="results">Results</h2>
<p>I got to 40 out of 350 points.</p>
<p>Impressive IMHO. To be honest I could have got more but I did not
want to spend more than 5 bucks in openAI.</p>
<h2 id="lessons">Lessons</h2>
<p>The prompting is super important. When writing the game guide, it
took me some time to notice that it was including information about the
state of the current run instead of generic information about the game
and that was confusing to subsequent runs.</p>
<p>Observability is a fun thing to do. I keep the plain text file for
the memory so I can see what the AI beliefs are at the moment. I am
missing a lot of info, though.</p>
<p>Will I continue? Obviously. I really think I can get this to win the
game somehow. The cost is a problem now. Probably I will set up
something using ollama or some local, even though slower, llm.</p>
<p>The adventure awaits</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-1.png" class="kg-image" loading="lazy" width="750" height="421" />
</figure>
]]></description>
    </item>
    <item>
      <title>Joyful shorts 03</title>
      <link>https://joy.pm/joyful-shorts-03/</link>
      <guid isPermaLink="true">https://joy.pm/joyful-shorts-03/</guid>
      <pubDate>Wed, 07 Jan 2026 23:56:29 +0000</pubDate>
      <description><![CDATA[<p>Another year, another shorts. Looks like we will be another
exploration year. We are having stuff like <a
href="https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum"
rel="noreferrer">ralph wiggum loops</a> in our lifes that still feel a
lot like we are throwing spaguetti to the wall to see what sticks. What
a time to be alive.</p>
<hr />
<p><a href="https://www.willienotwilly.com/contact-sheet-prompting"
rel="noreferrer">This post on how to workflow image generation</a> with
nano banana is amazing. It is giving me a lot of inspiration for some
tooling I want to create for my children.</p>
<p>Alcohol consumption is decreasing but there are unintended
consequences. <a href="https://www.freddyvega.com/p/alcohol"
class="uri">https://www.freddyvega.com/p/alcohol</a></p>
<p>I read <a
href="https://www.reddit.com/r/confession/comments/1q1mzej/im_a_developer_for_a_major_food_delivery_app_the/"
rel="noreferrer">a post in Reddit about a desperation score</a> and some
dark patterns some companies used. To be honest, having worked in the
food delivery industry that sounded like an absurd thing to me and <a
href="https://www.platformer.news/fake-uber-eats-whisleblower-hoax-debunked/"
rel="noreferrer">it finally happened to be a hoax</a>. It was fun to
read how the scammer used AI to fake his evidence... Come on, a company
using latex for its documentation?</p>
<p>I've been using <a
href="https://x.com/trq212/status/2005315279455142243"
rel="noreferrer">this form of interviewing lately to refine specs</a>.
It is a nice way of using AskUserQuestion. Another way of using claude
as a rubber duck.</p>
<p>I have a lot of fatigue on the concept of personal productivity. <a
href="https://x.com/EXM7777/status/2008571916307566800"
rel="noreferrer">This piece</a> summarizes a bit how I tend to feel
about it.</p>
]]></description>
    </item>
    <item>
      <title>Leveraging neovim as a TUI</title>
      <link>https://joy.pm/leveraging-neovim-as-a-tui/</link>
      <guid isPermaLink="true">https://joy.pm/leveraging-neovim-as-a-tui/</guid>
      <pubDate>Wed, 24 Dec 2025 08:07:18 +0000</pubDate>
      <description><![CDATA[<p>I tend to be much faster using git from a TUI instead from using the
commands in the terminal. Not exactly when doing add or commit but I,
very much enjoy reviewing the code and checking the diff. I know I
know... the good old git diff or diff-so-fancy are cool but I am a
person of habits.</p>
<p>My biggest use case is that I normally want to break the work I've
done into more than one commit and I'd like to see how everything is
looking like meanwhile I work.</p>
<p>It just happened that there is <a
href="https://github.com/gitui-org/gitui/issues/1353" rel="noreferrer">a
bug in gitui</a> that happened to be a problem for me. I am using
bitwarden as ssh-agent (not 100% happy with that yet) and I neet to
manually specify which key to use for some sites. Then opportunity
appeared.</p>
<p>Why not using the same git TUI for the shell and for Neovim?</p>
<p>I settled on <a href="https://github.com/NeogitOrg/neogit"
rel="noreferrer">Neogit</a> since it looks like my good old beloved <a
href="https://magit.vc/" rel="noreferrer">Magit</a>. Then starting a new
neogit session is as easy as:</p>
<pre class="shell"><code>alias g = nvim -c &quot;lua require(&#39;neogit&#39;).open()&quot; -c &quot;autocmd TabClosed * if tabpagenr(&#39;$&#39;) == 1 | quit | endif&quot;</code></pre>
<p>The alias also sets a callback so that, if I press 'q' I go back to
the shell automatically</p>
<p>This way I am using the same tool in when I am using the shell and
when I am coding using neovim. Cool!</p>
]]></description>
    </item>
    <item>
      <title>Joyful shorts 02</title>
      <link>https://joy.pm/joyful-shorts-02/</link>
      <guid isPermaLink="true">https://joy.pm/joyful-shorts-02/</guid>
      <pubDate>Tue, 23 Dec 2025 08:58:13 +0000</pubDate>
      <description><![CDATA[<p>I'm still getting the habit of writing this small form content. It is
very interesting because, to be honest, it looks to me like a small
effort format that gives space between longer form content that requires
more thinking and time in front of the machine.</p>
<p><a href="https://blog.joinmastodon.org/2025/11/mastodon-4.5/"
rel="noreferrer">The latest mastodon update brought quotes</a>). I'm
super happy to see mastodon community advancing in a healthy state.</p>
<p><a
href="https://cloud.google.com/blog/products/ai-machine-learning/announcing-official-mcp-support-for-google-services"
rel="noreferrer">Google releases MCP for all its services</a>. I need
some time to experiment with this but I hope I can do nasty things with
drive.</p>
<p><a href="https://agentskills.io/home" rel="noreferrer">Skills are now
an open standard</a>). It is cool to see some open standards to be
proliferating on this new AI world. I am still concerned on how portable
we should be from one LLM client to another (spoiler alert, it should be
100% portable with no friction).</p>
<p>Questionable ethics aside Anna's blog is always super interesting to
read for technical details. <a
href="https://annas-archive.li/blog/backing-up-spotify.html"
rel="noreferrer">This post on their efforts to backup Spotify</a> is no
exception.</p>
<p>I've been playing with text based adventures recently (blog post
incoming), but it looks like there is <a
href="https://huggingface.co/blog/textquests" rel="noreferrer">smarter
people than me doing it already</a>.</p>
<p>Even if you don’t train models, walking through <a
href="https://huggingface.co/spaces/OpenEvals/evaluation-guidebook"
rel="noreferrer">the llm evaluation guide</a> will give you a lot of
ideas. It is amazing that this kind of content is still free.</p>
]]></description>
    </item>
    <item>
      <title>Ai skills is now an open standard</title>
      <link>https://joy.pm/ai-skills-is-now-an-open-standard/</link>
      <guid isPermaLink="true">https://joy.pm/ai-skills-is-now-an-open-standard/</guid>
      <pubDate>Sat, 20 Dec 2025 08:46:33 +0000</pubDate>
      <description><![CDATA[<p>When you are coding one of the most interesting things you can do is
to define abstractions that work as a black box. That helps you thinking
at a higher level reducing the cognitive load on the problems you have
today.</p>
<p>Today we have a portable way to do that between llm clients: <a
href="https://agentskills.io/home" rel="noreferrer">the skills</a>.
Skills are able to do something similar to this in llm clients. We only
have to win having an open standard for this.</p>
<p>I will finally take my markdown folders and convert them to this
format.</p>
<p>This will, for sure be a popular way to automate internal
processes.</p>
]]></description>
    </item>
    <item>
      <title>Joyful shorts 01</title>
      <link>https://joy.pm/shorts-of-the-week/</link>
      <guid isPermaLink="true">https://joy.pm/shorts-of-the-week/</guid>
      <pubDate>Sat, 06 Dec 2025 20:17:02 +0000</pubDate>
      <description><![CDATA[<p>One of the reasons for me to have the excuse not to write is that I
have nothing to write about. There are a lot of topics you can almost
tweet about but not write a full blog post. I saw that <a
href="https://thorstenball.com/">Thorsten Ball</a> is writing normally a
weekly post gathering all those small topics and reads. I will,
unapologetically, copy that practice. He publishes that as a newsletter
too. I will just write the blog.</p>
<p><a href="https://twink.men/notes/aegnblhljnny02j9">The real
chameleons</a>...</p>
<p>I am finding myself lately using more and more GUI tools. Maybe
because in my day job I am using more MacOS but probably because the
nature of the things I am trying to do is changing a little bit. I used
to rely a lot in saving terminal sessions but these days I am using more
OBS or Gifox than ever. Anyway I always find comforting having tools
like <a href="https://github.com/charmbracelet/vhs">vhs</a> available.
They give peace of mind.</p>
<p>Another nice episode of <a
href="https://pca.st/episode/339e87ab-f58f-4253-bf29-05255d0744a5">The
cyberpunk librarian</a>. Talking about personal freedom and self
hosting.</p>
<p>This week I went through the first chapter of <a
href="https://books.google.es/books?id=0x_vAAAAMAAJ">Casella and
Berger</a>. I am supposed to know those terms but it is funny how
reading a technical text makes you settle all your concepts in such a
way that unless you are not 100% clear in the concepts it is super hard
to follow. I guess I will spend a couple of weeks in the
exercises...</p>
<p><a href="https://www.youtube.com/watch?v=CJZp41mJuCQ">This interview
in Freakonomics</a> with Dan Wang about his book Breakneck has an
amazing metaphor I will use more. He talks about lawyer mindset vs
engineer mindset. The engineer has problems and tries to solve them.
What he calls “the lawyer mindset” is that you are thinking in the
process of solving the problem instead. Defining a good process becomes
more important and hurdles are found for the people actually doing the
work.</p>
<p>I, briefly, tried Antigravity and I was far from impressed. After
watching <a href="https://youtu.be/nTOVIGsqCuY?si=hQ3wkkazzE3xgM1Y">this
video</a> I am starting to question myself.</p>
]]></description>
    </item>
    <item>
      <title>I'm Finally Happy with My Mac Setup: An Arch Linux User's Journey</title>
      <link>https://joy.pm/im-finally-happy-with-my-mac-setup-an-arch-linux-users-journey/</link>
      <guid isPermaLink="true">https://joy.pm/im-finally-happy-with-my-mac-setup-an-arch-linux-users-journey/</guid>
      <pubDate>Sun, 29 Dec 2024 15:17:36 +0000</pubDate>
      <description><![CDATA[<p>This will be a quick post. I am just enumerating all the tools I am
using on my mac to avoid missing Arch too much. For work related reasons
I moved to a M2 mac four months ago so I had to adapt a ton of
stuff.</p>
<h2 id="the-non-negotiables">The Non-Negotiables</h2>
<p>First, the good news: many of my essential tools (Neovim, VSCode,
etc.) are cross-platform, so they came along for the ride. But some
aspects of my workflow needed Mac-specific alternatives. Let's dive into
what I'm using to make macOS feel more like home.</p>
<p>Without further ado, my picks:</p>
<h2 id="aerospace-the-i3-like-tiling-window-manager">AeroSpace: The
i3-like Tiling Window Manager</h2>
<p><a href="https://github.com/nikitabobko/AeroSpace">GitHub -
nikitabobko/AeroSpace</a></p>
<p>The biggest gap to fill was a proper tiling window manager. <a
href="https://github.com/nikitabobko/AeroSpace">AeroSpace</a> has become
my go-to solution. While it's not as configurable as XMonad, it brings
several i3-like features that make it a solid alternative.</p>
<p>Aerospace does something insanely well. Something that grinds my
gears in mac is that the animations on workspaces take about 500ms which
is a huge waste of time. This completely ignores macos workspaces with
its own implementation. Huge success.</p>
<h3 id="sort-of-scratchpads">Sort of scratchpads</h3>
<p>Scratchpads are probably the feature I liked the most in XMonad. You
can anyhow create a back and forth workspace so you can have something
similar. Not perfect but good enough.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode lua"><code class="sourceCode lua"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="va">cmd</span><span class="op">-</span><span class="va">o</span> <span class="op">=</span> <span class="st">&#39;workspace --auto-back-and-forth O&#39;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="vs">[[on-window-detected]]</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="va">if</span><span class="op">.</span><span class="va">app</span><span class="op">-</span><span class="va">id</span> <span class="op">=</span> <span class="st">&#39;md.obsidian&#39;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="va">run</span> <span class="op">=</span> <span class="st">&#39;move-node-to-workspace O&#39;</span></span></code></pre></div>
<p>With this I can have press cmd-o to go to obsidian workspace and
pressing cmd-o again takes you back.</p>
<h3 id="keyboard-driven-window-focus">Keyboard driven window focus</h3>
<p>And then set some shortcuts to focus common apps.</p>
<p>To do that I created a ~/.config/aerospace/focus.sh script that
does:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env bash</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="va">APP_NAME</span><span class="op">=</span><span class="va">$1</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="va">app_window_id</span><span class="op">=</span><span class="va">$(</span><span class="ex">aerospace</span> list-windows <span class="at">--all</span> <span class="at">--format</span> <span class="st">&quot;%{window-id}%{right-padding} | %{app-name}&quot;</span> <span class="kw">|</span> <span class="fu">grep</span> <span class="va">$APP_NAME</span> <span class="kw">|</span> <span class="fu">cut</span> <span class="at">-d</span><span class="st">&#39; &#39;</span> <span class="at">-f1</span> <span class="kw">|</span> <span class="fu">sed</span> <span class="st">&#39;1p;d&#39;</span><span class="va">)</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="ex">aerospace</span> focus <span class="at">--window-id</span> <span class="va">$app_window_id</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="ex">aerospace</span> move-mouse window-lazy-center</span></code></pre></div>
<p>With that I can have the shortcut not to only focus on the app I want
but move the mouse cursor to the middle of the window so I can deduct
where the mouse is faster. I just have to add to the aerospace.toml the
following</p>
<pre><code>cmd-y = &#39;exec-and-forget ~/.config/aerospace/focus.sh Code&#39;
cmd-u = &#39;exec-and-forget ~/.config/aerospace/focus.sh kitty&#39;
cmd-i = &#39;exec-and-forget ~/.config/aerospace/focus.sh Firefox&#39;</code></pre>
<h3 id="small-tweaks">Small tweaks</h3>
<p>I had to force Firefox's picture in picture to float</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode lua"><code class="sourceCode lua"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="vs">[[on-window-detected]]</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="va">if</span><span class="op">.</span><span class="va">app</span><span class="op">-</span><span class="va">id</span> <span class="op">=</span> <span class="st">&#39;org.mozilla.firefox&#39;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="va">if</span><span class="op">.</span><span class="va">window</span><span class="op">-</span><span class="va">title</span><span class="op">-</span><span class="va">regex</span><span class="op">-</span><span class="va">substring</span> <span class="op">=</span> <span class="st">&#39;Picture-in-Picture&#39;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="va">run</span> <span class="op">=</span> <span class="st">&#39;layout floating&#39;</span></span></code></pre></div>
<p>And then set some shortcuts to focus common apps.</p>
<h2 id="ice">Ice</h2>
<p><a href="https://github.com/jordanbaird/Ice">GitHub -
jordanbaird/Ice</a></p>
<p>I've used mac in the past and you end up with 9000 different menubar
items you don't care about so using ice makes it easy for you to not
clutter your menubar.</p>
<h2 id="alfred">Alfred</h2>
<p><a href="https://www.alfredapp.com/">Alfred - Productivity App for
macOS</a></p>
<p>There are more modern options but I had a perpetual license for this
since forever. I don't want another subscription for this so Raycast was
out of the question. If you combine with stuff like <a
href="https://github.com/yohasebe/openai-chat-api-workflow"
class="uri">https://github.com/yohasebe/openai-chat-api-workflow</a> you
will have more than enough (text, audio and vision) with paying just for
API usage.</p>
<h2 id="notable-alternatives">Notable alternatives</h2>
<p>There are some software I don't want to avoid to mention but in the
end it did not make the cut.</p>
<h3 id="apptivate">apptivate</h3>
<p><a href="http://www.apptivateapp.com/">Apptivate</a></p>
<p>To recover from the lack of scratchpads this was my first option.
Anyhow it looks insanely outdated. It has some cool options, like
warning you if you are overriding a system shortcut.</p>
<h3 id="yabai">yabai</h3>
<p><a href="https://github.com/koekeishiya/yabai">GitHub -
koekeishiya/yabai</a></p>
<p>Yabai supports proper scratchpads but I have not been able to set up
workspaces that work instantly and I'd have to disable SIP. Anyway, it
is super cool and maybe some of you find it useful.</p>
<h3 id="hammerspoon">Hammerspoon</h3>
<p><a href="https://www.hammerspoon.org/">Hammerspoon</a></p>
<p>A swiss army knife for automation. You can assign lua functions to
events. I tried to use it to have keybindings to focus to apps like:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode lua"><code class="sourceCode lua"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Function to focus on app or launch it if it&#39;s not running</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> focusOrLaunch<span class="op">(</span><span class="va">appName</span><span class="op">,</span> <span class="va">bundleID</span><span class="op">)</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">local</span> <span class="va">app</span> <span class="op">=</span> <span class="va">hs</span><span class="op">.</span><span class="va">application</span><span class="op">.</span>get<span class="op">(</span><span class="va">appName</span><span class="op">)</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="va">app</span> <span class="cf">then</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>        <span class="cf">if</span> <span class="va">app</span><span class="op">:</span>isFrontmost<span class="op">()</span> <span class="cf">then</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>            <span class="co">-- If the app is already focused, do nothing</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>            <span class="cf">return</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>        <span class="cf">else</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>            <span class="co">-- Focus on the app</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>            <span class="va">app</span><span class="op">:</span>activate<span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>            <span class="va">app</span><span class="op">:</span>unhide<span class="op">()</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>        <span class="cf">end</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    <span class="cf">else</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- Launch the app if it&#39;s not running</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>        <span class="va">hs</span><span class="op">.</span><span class="va">application</span><span class="op">.</span>launchOrFocusByBundleID<span class="op">(</span><span class="va">bundleID</span><span class="op">)</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>    <span class="cf">end</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a><span class="co">-- Define keyboard shortcuts</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="co">-- VSCode</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="va">hs</span><span class="op">.</span><span class="va">hotkey</span><span class="op">.</span>bind<span class="op">({</span><span class="st">&quot;cmd&quot;</span><span class="op">},</span> <span class="st">&quot;y&quot;</span><span class="op">,</span> <span class="kw">function</span><span class="op">()</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>    focusOrLaunch<span class="op">(</span><span class="st">&quot;Code&quot;</span><span class="op">,</span> <span class="st">&quot;com.microsoft.VSCode&quot;</span><span class="op">)</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span><span class="op">)</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="co">-- Kitty</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a><span class="va">hs</span><span class="op">.</span><span class="va">hotkey</span><span class="op">.</span>bind<span class="op">({</span><span class="st">&quot;cmd&quot;</span><span class="op">},</span> <span class="st">&quot;u&quot;</span><span class="op">,</span> <span class="kw">function</span><span class="op">()</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a>    focusOrLaunch<span class="op">(</span><span class="st">&quot;kitty&quot;</span><span class="op">,</span> <span class="st">&quot;net.kovidgoyal.kitty&quot;</span><span class="op">)</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span><span class="op">)</span></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a><span class="co">-- Firefox</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a><span class="va">hs</span><span class="op">.</span><span class="va">hotkey</span><span class="op">.</span>bind<span class="op">({</span><span class="st">&quot;cmd&quot;</span><span class="op">},</span> <span class="st">&quot;i&quot;</span><span class="op">,</span> <span class="kw">function</span><span class="op">()</span></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a>    focusOrLaunch<span class="op">(</span><span class="st">&quot;Firefox&quot;</span><span class="op">,</span> <span class="st">&quot;org.mozilla.firefox&quot;</span><span class="op">)</span></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span><span class="op">)</span></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a><span class="co">-- Obsidian</span></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a><span class="va">hs</span><span class="op">.</span><span class="va">hotkey</span><span class="op">.</span>bind<span class="op">({</span><span class="st">&quot;cmd&quot;</span><span class="op">},</span> <span class="st">&quot;o&quot;</span><span class="op">,</span> <span class="kw">function</span><span class="op">()</span></span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a>    focusOrLaunch<span class="op">(</span><span class="st">&quot;Obsidian&quot;</span><span class="op">,</span> <span class="st">&quot;md.obsidian&quot;</span><span class="op">)</span></span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span><span class="op">)</span></span></code></pre></div>
<p>But I ended up leveraging aerospace for this so one less software to
run.</p>
<h2 id="the-veredict">The veredict</h2>
<p>While it's not exactly Arch Linux, this setup has made macOS feel
surprisingly comfortable. I hope I gave you some ideas on this post on
stuff you can do to feel a little bit better using your good old MacOS
computer.</p>
<p>Have fun!</p>
]]></description>
    </item>
    <item>
      <title>Data informed vs Data driven management</title>
      <link>https://joy.pm/data-informed-vs-data-driven/</link>
      <guid isPermaLink="true">https://joy.pm/data-informed-vs-data-driven/</guid>
      <pubDate>Sun, 28 Jul 2024 09:03:40 +0000</pubDate>
      <description><![CDATA[<p>To be honest I was about to title this post "How to fuck up with your
data" but at the end of the day...</p>
<p>I like data. I love data. Data is cool and necessary. And toxic if
used wrongly.</p>
<p>"Metrics are fine so everything is fine". I am tired of seeing
organizations and people to rely on metrics to do the job for them.</p>
<p>Metrics are becoming the opium of management. We forgot why we do
stuff and instead focus on creating the feeling that everything is
right.</p>
<p>I saw a message in a management message board saying that we have to
move some people's ratings to another value so all values fit a bell
curve. That is wrong in so many levels that I cannot deal with it. I
have to write a blog post just in case I can help training whatever AI
so less people act as dumb-asses.</p>
<p>Let's deal with this example.</p>
<p>In a healthy, big enough, organization performance ratings of the
employees should be distributed on a bell curve. That means that
everyone is getting enough of a challenge and statistics should do the
rest.</p>
<figure class="kg-card kg-image-card">
<img src="https://factohr-1a56a.kxcdn.com/wp-content/themes/factohr-theme/images/glossary/bell-curve/Bell-Curve.png" class="kg-image" loading="lazy" width="720" height="540" alt="Bell Curve | Know The Meaning and FAQs | factoHR" />
</figure>
<p>You will have low performers for many reasons. I expect a person in a
new role to perform poorly by definition. It makes sense and no one
needs to worry about that. Also, everyone blips from time to time. We
are all humans. Some cases are not reasonable and in a healthy
organization we should not be scared of talking about them.</p>
<p>Not it comes the toxic part. When an organization tries to
artificially fit their ratings to this curve for whatever reasons they
are playing with the stats. They are losing every single reason you have
for the bell curve to exist.</p>
<p>A wrong bell curve is not fixed by artificially making people stick
to it. If there are some toxic elements in culture that make an
organization not to stick to a bell curve they should be seen, known and
communicated with big neon signs.</p>
<blockquote>
<p>A wrong bell curve is a consequence, not a symptom.</p>
</blockquote>
<p>Normally you compare departments and you expect all of them to share
the same shape. That means every person is as fair as you can and
everyone shares a common view of what a given rating is.</p>
<p>You don't assign quotas of ratings to people. That is insanely absurd
and even more toxic than a wrong curve. You are putting incentives to
deceive the system and just don't do the right thing but the thing that
looks good instead. Let's put everyone in the middle so everyone has a
happy face! You are creating a full management layer that don't have to
take decisions rendering them useless.</p>
<p>Also, if you assign quotas you don't detect abnormal situations WHICH
IS THE PURPOSE OF DOING THIS BLOODY EXERCISE.</p>
<p>Bell curves should appear with no "manual intervention". You should
not be tricking the numbers. If everyone in the organization rates the
same way they will appear and if they don't then you need to train
people so all share the same view. You use the metric to be informed
when you solved the problem, to detect places where you can act and not
as a substitute for problem solving.</p>
<p>That is why I tend to use data informed instead of data driven. You
want data as signals that you are right on track but they are not the
goal itself. The map is not the territory.</p>
]]></description>
    </item>
    <item>
      <title>Post mortem: 2024-05-25</title>
      <link>https://joy.pm/post-mortem-2024/</link>
      <guid isPermaLink="true">https://joy.pm/post-mortem-2024/</guid>
      <pubDate>Mon, 27 May 2024 14:41:12 +0000</pubDate>
      <description><![CDATA[<p>For reference you can see a bit of <a
href="__GHOST_URL__/homelab-version-2024/" rel="noreferrer">the
organization of my sites in this same blog</a>.</p>
<p>At 4am evilmeow.com and all my sites (including this log started
alerting)</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="./assets/image-11.png" class="kg-image" loading="lazy" width="1659" height="1245" />
<figcaption>
<span style="white-space: pre-wrap;">You can already see the 5h of
downtime. Funny, isn’t it?</span>
</figcaption>
</figure>
<p>Ping failing so let's see.</p>
<p>A restart "fixed" the problem for a couple of minutes and then it got
down again.</p>
<p>The logs for some reason were not being sent to Loki. So we
redirected dmesg to a file. Checking that file we saw the error
message:</p>
<pre><code>&gt; [170902.115198] watchdog: BUG: soft lockup - CPU#0 stuck for 7342s! [CPU
&gt; 1/KVM:1382]
&gt;
&gt; [170986.118899]  ? __hrtimer_run_queues+0x105/0x280
&gt; [170986.118986]  ? clockevents_program_event+0xb3/0x140
&gt; [170986.119112]  ? hrtimer_interrupt+0xf6/0x250
&gt; [170986.119199]  ? __sysvec_apic_timer_interrupt+0x4e/0x150
&gt; [170986.119288]  ? sysvec_apic_timer_interrupt+0x8d/0xd0
&gt; [170986.119378]  &lt;/IRQ&gt;
&gt; [170986.119460]  &lt;TASK&gt;
&gt; [170986.119543]  ? asm_sysvec_apic_timer_interrupt+0x1b/0x20
&gt; [170986.119634]  ? smp_call_function_single+0x11e/0x160
&gt; [170986.119727]  ? __pfx___loaded_vmcs_clear+0x10/0x10 [kvm_intel]
&gt; [170986.119840]  ? update_load_avg+0x82/0x830
&gt; [170986.119928]  vmx_vcpu_load_vmcs+0x143/0x440 [kvm_intel]
&gt; [170986.120037]  vmx_vcpu_load+0x19/0x60 [kvm_intel]
&gt; [170986.120145]  kvm_arch_vcpu_load+0x48/0x240 [kvm]
&gt; [170986.120320]  ? load_fixmap_gdt+0x44/0x80
&gt; [170986.120407]  ? __perf_event_task_sched_in+0x88/0x200
&gt; [170986.120497]  kvm_sched_in+0x3a/0x50 [kvm]
&gt; [170986.120649]  finish_task_switch.isra.0+0x212/0x310
&gt; [170986.120738]  __schedule+0x409/0x15e0
&gt; [170986.120825]  schedule+0x33/0x110
&gt; [170986.120910]  kvm_vcpu_block+0x58/0xc0 [kvm]
&gt; [170986.121094]  kvm_vcpu_halt+0xde/0x460 [kvm]
&gt; [170986.121246]  kvm_arch_vcpu_ioctl_run+0xab4/0x1760 [kvm]
&gt; [170986.121409]  kvm_vcpu_ioctl+0x297/0x800 [kvm]
&gt; [170986.121591]  ? kvm_on_user_return+0x78/0xd0 [kvm]
&gt; [170986.121749]  ? fire_user_return_notifiers+0x37/0x80
&gt; [170986.121842]  ? syscall_exit_to_user_mode+0x86/0x260
&gt; [170986.121931]  __x64_sys_ioctl+0xa0/0xf0
&gt; [170986.122023]  x64_sys_call+0xa68/0x24b0
&gt; [170986.122132]  do_syscall_64+0x81/0x170
&gt; [170986.122241]  ? do_syscall_64+0x8d/0x170
&gt; [170986.122328]  ? do_syscall_64+0x8d/0x170
&gt; [170986.122414]  ? do_syscall_64+0x8d/0x170
&gt; [170986.122500]  ? do_syscall_64+0x8d/0x170
&gt; [170986.122586]  ? do_syscall_64+0x8d/0x170
&gt; [170986.122673]  entry_SYSCALL_64_after_hwframe+0x78/0x80
&gt; [170986.122761] RIP: 0033:0x78d9dcb1cc5b
&gt; [170986.122866] Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00
&gt; 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 &lt;89&gt; c2 3d 00 f0
&gt; ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
&gt; [170986.123004] RSP: 002b:000078d9ca3fb0b0 EFLAGS: 00000246 ORIG_RAX:
&gt; 0000000000000010
&gt; [170986.123109] RAX: ffffffffffffffda RBX: 000058fa7d6bf4a0 RCX: 000078d9dcb1cc5b
&gt; [170986.123213] RDX: 0000000000000000 RSI: 000000000000ae80 RDI: 000000000000001d
&gt; [170986.123318] RBP: 000000000000ae80 R08: 000058fa7b0f2c90 R09: 0000000000000000
&gt; [170986.123422] R10: 0000000000000002 R11: 0000000000000246 R12: 0000000000000000
&gt; [170986.123526] R13: 0000000000000001 R14: 0000000000000001 R15: 0000000000000000
&gt; [170986.123633]  &lt;/TASK&gt;
&gt; [171000.004671] rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
&gt; [171000.004778] rcu:  7-...0: (0 ticks this GP) idle=1224/1/0x4000000000000000
&gt; softirq=2170915/2170915 fqs=3901860
&gt; [171000.004894] rcu:          hardirqs   softirqs   csw/system
&gt; [171000.004982] rcu:  number:        0          0            0
&gt; [171000.005072] rcu:  cputime:        0          0            0   ==&gt; 7950223(ms)
&gt; [171000.005176] rcu:  (detected by 0, t=7980227 jiffies, g=5655105, q=231104
&gt; ncpus=8)
&gt; [171000.005283] Sending NMI from CPU 0 to CPUs 7:
&gt; [171000.005373] NMI backtrace for cpu 7
&gt; [171000.005375] CPU: 7 PID: 391169 Comm: kworker/7:0 Tainted: G      D W I  L   
&gt; 6.8.4-3-pve #1
&gt; [171000.005376] Hardware name: FUJITSU D3417-B1/D3417-B1, BIOS V5.0.0.11
&gt; R1.29.0.SR.1 for D3417-B1x               05/13/2020
&gt; [171000.005378] Workqueue: md submit_flushes
&gt; [171000.005383] RIP: 0010:native_queued_spin_lock_slowpath+0x7f/0x2d0
&gt; [171000.005386] Code: 00 00 f0 0f ba 2b 08 0f 92 c2 8b 03 0f b6 d2 c1 e2 08 30 e4
&gt; 09 d0 3d ff 00 00 00 77 5f 85 c0 74 10 0f b6 03 84 c0 74 09 f3 90 &lt;0f&gt; b6 03 84 c0
&gt; 75 f7 b8 01 00 00 00 66 89 03 5b 41 5c 41 5d 41 5e</code></pre>
<p>After reaching Hetzner support looks like there is a hardware failure
and they will proceed to replace the full machine.</p>
<p>They provide access to the new machine and I proceed to restoring
latest backup from Proxmox Backup Server.</p>
<p>I started with OPNSense. Backup was restored in 5 mins but the
machine did not start.</p>
<p>The interface vmbr1 was off. I lost 1h debugging that. By default
Debian's installation of Proxmox does not include the package
<code>openvswitch-switch</code> and looks like I installed it manually
and forgot to add it to the automations 🤦‍♂️.</p>
<p>Once the interface was in place everything started smooth as butter.…
until 4 hours later.</p>
<p>The machine started failing again with a different dmesg error</p>
<pre><code>&gt; [31719.674017] BUG: kernel NULL pointer dereference, address: 0000000000000008
&gt; [31719.674118] #PF: supervisor write access in kernel mode
&gt; [31719.674197] #PF: error_code(0x0002) - not-present page
&gt; [31719.674305] PGD 0 P4D 0
&gt; [31719.674381] Oops: 0002 [#1] PREEMPT SMP PTI
&gt; [31719.674460] CPU: 0 PID: 1180 Comm: kvm Tainted: G          I        6.8.4-3-pve
&gt; #1
&gt; [31719.674568] Hardware name: FUJITSU D3417-B1/D3417-B1, BIOS V5.0.0.11
&gt; R1.29.0.SR.1 for D3417-B1x               05/13/2020
&gt; [31719.674669] RIP: 0010:blk_flush_complete_seq+0x291/0x2d0
&gt; [31719.674775] Code: 0f b6 f6 49 8d 56 01 49 c1 e6 04 4d 01 ee 48 c1 e2 04 49 8b
&gt; 4e 10 4c 01 ea 48 39 ca 74 2b 48 8b 4b 50 48 8b 7b 48 48 8d 73 48 &lt;48&gt; 89 4f 08 48
&gt; 89 39 49 8b 4e 18 49 89 76 18 48 89 53 48 48 89 4b
&gt; [31719.674904] RSP: 0018:ffffaea0c936ba60 EFLAGS: 00010046
&gt; [31719.674984] RAX: 0000000000000000 RBX: ffff9808d4040a00 RCX: ffff9808d4040a48
&gt; [31719.675067] RDX: ffff9808d202b420 RSI: ffff9808d4040a48 RDI: 0000000000000000
&gt; [31719.675149] RBP: ffffaea0c936baa0 R08: 0000000000000000 R09: 0000000000000000
&gt; [31719.675231] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000029801
&gt; [31719.675366] R13: ffff9808d202b400 R14: ffff9808d202b410 R15: ffff9808d3e97300
&gt; [31719.675449] FS:  000073172a4006c0(0000) GS:ffff9817ae200000(0000)
&gt; knlGS:0000000000000000
&gt; [31719.675546] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
&gt; [31719.675626] CR2: 0000000000000008 CR3: 000000010af80002 CR4: 00000000003726f0
&gt; [31719.675708] Call Trace:
&gt; [31719.675808]  &lt;TASK&gt;</code></pre>
<p>This time anyway it looks like it is not a hardware issue accorting
to Hetzner support. I rolled back the kernel to a previous version and
it has been running flawlessly for a day.</p>
<h1 id="takeaways">Takeaways</h1>
<ul>
<li>Every small thing you don't automate bites you in the ass. Even if
it is just installing a package</li>
<li>I am a bit concerned on the stability on the kernel in Hetzner
machines. I will keep two of them installed in case I have to roll
back.</li>
<li>Having decent logging aggregation saved me again.</li>
</ul>
<p>So, some more learnings in exchange for some downtime. Anyway I am
relatively happy now and sure that in the case of catastrophic event I
won't lose all the data. Another step in the path to making evilmeow.com
open to the public.</p>
]]></description>
    </item>
    <item>
      <title>An intuitive grasp over Fourier Transform</title>
      <link>https://joy.pm/an-intuitive-grasp-over-fourier-transform/</link>
      <guid isPermaLink="true">https://joy.pm/an-intuitive-grasp-over-fourier-transform/</guid>
      <pubDate>Sun, 26 May 2024 10:51:34 +0000</pubDate>
      <description><![CDATA[<p>I have a lot of things in my life I've always wanted to understand
but for some reason the maths were too hard for me. One of my good old
nemesis was the Fourier Transform</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="./assets/image-1.png" class="kg-image" loading="lazy" width="409" height="125" />
<figcaption>
<span style="white-space: pre-wrap;">Scary as fuck</span>
</figcaption>
</figure>
<p>I am not that good at mathematical abstractions so in order to
understand something I prefer to take the opposite path and then see the
reason of it. Why is it useful? Why all the fuss?</p>
<p>A Fourier transform is a way to decompose a signal into its
components. If we have a signal with frequency of 5Hz and another with
50Hz combined I will have something like</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> matplotlib.pyplot <span class="im">as</span> plt</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.fftpack <span class="im">import</span> fft, fftfreq</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> signal(t):</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> np.sin(<span class="dv">2</span> <span class="op">*</span> np.pi <span class="op">*</span> <span class="dv">5</span> <span class="op">*</span> t) <span class="op">+</span> <span class="fl">0.5</span> <span class="op">*</span> np.sin(<span class="dv">2</span> <span class="op">*</span> np.pi <span class="op">*</span> <span class="dv">50</span> <span class="op">*</span> t)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>sampling_rate <span class="op">=</span> <span class="dv">1000</span>  </span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>T <span class="op">=</span> <span class="fl">1.0</span> <span class="op">/</span> sampling_rate  </span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>t <span class="op">=</span> np.arange(<span class="dv">0</span>, <span class="fl">1.0</span>, T)  </span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>y <span class="op">=</span> signal(t)  </span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>Y <span class="op">=</span> fft(y)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>N <span class="op">=</span> <span class="bu">len</span>(t)</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>freqs <span class="op">=</span> fftfreq(N, T)</span></code></pre></div>
<p>Let's draw this signal</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>plt.figure(figsize<span class="op">=</span>(<span class="dv">14</span>, <span class="dv">6</span>))</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>plt.plot(t, y)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>plt.title(<span class="st">&#39;Domain of time&#39;</span>)</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>plt.xlabel(<span class="st">&#39;Time (s)&#39;</span>)</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>plt.ylabel(<span class="st">&#39;Amplitude&#39;</span>)</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>plt.grid()</span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/image-2.png" class="kg-image" loading="lazy" width="1162" height="544" />
</figure>
<p>You can see the big wave to be the 5Hz signal and the much faster
wave the 50Hz signal.</p>
<p>If I apply Fourier transform to that and plot the absolute value of
the result I will have something like.</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>plt.figure(figsize<span class="op">=</span>(<span class="dv">14</span>, <span class="dv">6</span>))</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>plt.plot(freqs[freqs<span class="op">&gt;</span><span class="dv">0</span>], np.<span class="bu">abs</span>(Y)[freqs<span class="op">&gt;</span><span class="dv">0</span>])</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>plt.title(<span class="st">&#39;Domain of frequency&#39;</span>)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>plt.xlabel(<span class="st">&#39;Frecuency (Hz)&#39;</span>)</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>plt.ylabel(<span class="st">&#39;Magnitude&#39;</span>)</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>plt.grid()</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>plt.show()</span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/image-5.png" class="kg-image" loading="lazy" width="1195" height="548" />
</figure>
<p>There are two peaks. One at 5 and another at 50. If we draw with
stems it will be more clear</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-6.png" class="kg-image" loading="lazy" width="1201" height="525" />
</figure>
<p>Exactly the ones we generated the wave from. But... still... why is
this useful?</p>
<p>There are two things that I didn't explain yet:</p>
<ul>
<li>We can also do this for sampled signals, not only for continuous
ones</li>
<li>This is a two way transform</li>
</ul>
<p>Let's start with the former. I can do Fourier transform on, for
example a wav file. A wav file are sound wave samples at a given
frequency (normally 44000 or 48000 Hz) and decompose them. We can do the
same with color waves in an image or even whatever we think can behave
as a signal. That makes the potential applications endless.</p>
<p>This being a two way transform is the final piece of the puzzle. I
can go back and forth from Fourier transform to original signal. This
allows us to manipulate components of the signal with ease and get back
to the original.</p>
<p>In our previous example let's imagine that we consider the 50Hz
signal (the one that vibrates more) to be noise. Let's do a small noise
gate and filter that signal in the frequency domain</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>Y_filtered <span class="op">=</span> Y.copy()</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>Y_filtered[<span class="dv">50</span>] <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>Y_filtered[<span class="op">-</span><span class="dv">50</span>] <span class="op">=</span> <span class="dv">0</span></span></code></pre></div>
<p>This filtered signal will show as</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-8.png" class="kg-image" loading="lazy" width="1179" height="535" />
</figure>
<p>So if we do the inverse Fourier over it</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.fftpack <span class="im">import</span> ifft</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>filtered_y <span class="op">=</span> ifft(Y_filtered)</span></code></pre></div>
<p>We get</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-9.png" class="kg-image" loading="lazy" width="1212" height="545" />
</figure>
<p>We did something that can be improved, though, in the lines</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>Y_filtered[<span class="dv">50</span>] <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>Y_filtered[<span class="op">-</span><span class="dv">50</span>] <span class="op">=</span> <span class="dv">0</span></span></code></pre></div>
<p>In out code we have that array called freqs. Since the array indexes
are discrete values and a signal is a continuum we have this kind of
arrays so we can know what the indexes are. In reality that 50 we want
to filter is not an exact point but we have do do a small range. Let's
say we want to do 0.1 wide.</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>Y_filtered[np.<span class="bu">abs</span>(freqs <span class="op">-</span> <span class="dv">50</span>) <span class="op">&lt;</span> <span class="fl">0.1</span>] <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>Y_filtered[np.<span class="bu">abs</span>(freqs <span class="op">+</span> <span class="dv">50</span>) <span class="op">&lt;</span> <span class="fl">0.1</span>] <span class="op">=</span> <span class="dv">0</span></span></code></pre></div>
<p>That will set to zero all values that are at less than 0.1 to the
50Hz point. From 49999 to 50001. Probably we will have just one in our
array but depending on the resolution you may have more.</p>
<p>So now we should have some super basic intuition about how this
works, why is important and how we can use it.</p>
<p>Happy hacking!</p>
]]></description>
    </item>
    <item>
      <title>Homelab version 2024</title>
      <link>https://joy.pm/homelab-version-2024/</link>
      <guid isPermaLink="true">https://joy.pm/homelab-version-2024/</guid>
      <pubDate>Fri, 10 May 2024 12:14:59 +0000</pubDate>
      <description><![CDATA[<p>Hi! I’ve done the 100nt revamp of my homelab so I want to do a small
post to describe and document how everything is doing and where I wanna
go since I think this is relatively interesting.</p>
<p>First and foremost, do not forget what I want my homelab for. My
homelab is excessive, overkill, baroque... Do I need a Kubernetes
cluster? No, obviously not. Do I learn a lot by having and maintaining
it? Hell, yes!</p>
<p>My homelab is optimized for learning. I try stuff, I am wild, I am
naive and crazy. Sorry, not sorry.</p>
<p>Obviously it can be done better. I expect no less.</p>
<p>The sections are in no particular order.</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="./assets/Homelab.drawio.png" class="kg-image" loading="lazy" width="1158" height="738" />
<figcaption>
<span style="white-space: pre-wrap;">First version of my homelab
diagram</span>
</figcaption>
</figure>
<hr />
<h2 id="hardware-f09f948chardware">🔌Hardware
{#%F0%9F%94%8Chardware}</h2>
<p>I have a small Thinkcentre m90q kubernetes cluster. It is 3 machines.
All are a tiny bit different but they work fine together anyway.</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="./assets/image-4.png" class="kg-image" loading="lazy" width="788" height="735" />
<figcaption>
<span style="white-space: pre-wrap;">Cable management? Dust removal? No
way!</span>
</figcaption>
</figure>
<p>Next to that I have my good old Qnap with 4x8TB disks in RAID5.</p>
<figure class="kg-card kg-image-card kg-width-wide">
<img src="./assets/image-2.png" class="kg-image" loading="lazy" width="1699" height="1158" />
</figure>
<h3 id="section"></h3>
<p><a href="https://modcase.com.au/products/nas" rel="noreferrer">I 3d
printed a new homebrew NAS</a>. I want probably to ditch RAID for it.
RAID is not a backup. I learnt that the wrong way. RAID only provides
availability and just some extra reliability. I have to do the backups
anyway and not having the data available in NAS for some hours is
something I can definitely live with. Specially since I am moving the
data that needs to be available to Longhorn volumes in Kubernetes. More
on this later.</p>
<p>The new NAS is the fourth Proxmox node you see in the screenshot.</p>
<p>This one will also double down as Proxmox Backup Server.</p>
<hr />
<h2 id="networking-e2988eefb88f-networking">☎️ Networking
{#%E2%98%8E%EF%B8%8F-networking}</h2>
<p>I moved production to Hetzner but I don’t want to use straight their
platform. Maybe I am a bit cheap but I rented a Hetzner server and
installed OPNSense on it.</p>
<p>Since I took that path in my servers I bought a small pfSense box and
installed OPNSense on it. I use it to create a small Wireguard network
so I can see my production virtual machines locally to ease
debugging.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image-1.png" class="kg-image" loading="lazy" width="1225" height="260" />
</figure>
<p>This means that at home I can do
<code>ssh myuser@192.168.2.123</code> and I will see the machine as if
it was here. This is a bit more nuisance to set up at router level but
it simplifies a lot the configuration of the continuous integration and
deployment. I used to have VPN at computer level but setting this in my
router makes everything far easier.</p>
<p>Security is a concern anyway. I know now my home becomes a gigantic
security issue 🥲</p>
<h3 id="the-future">The future</h3>
<p>I use Wireguard tunnels to access my network from the outside but I
want to start giving access to some friends. I will probably have to
fight somehow for the IP address spaces and I don't know how to properly
solve that yet.</p>
<hr />
<h2 id="operating-systems">Operating systems</h2>
<p>I rely on Proxmox and bare Debian a lot. I know Proxmox is just
software over Debian but you know what I mean. I am starting to have all
my servers in plain Debian. Indeed moving to a homebrew NAS will mean
moving from QNap's propietary thing to Proxmox with Debian VMs.</p>
<p>I have, anyway exceptions. Since Ghost recommends Ubuntu is is using
it underneath.</p>
<hr />
<h2 id="general-tools">General tools</h2>
<p>So you can get a good view of the mess I am in I keep a Homepage
installation with links to a lot of stuff here. It looks a lot because
it is a lot but I think in a couple of iterations I can get a super fun
start page. At the moment the mess looks like this.</p>
<figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption">
<img src="./assets/image.png" class="kg-image" loading="lazy" width="2000" height="1174" />
<figcaption>
<span style="white-space: pre-wrap;">My startup page in my
browser</span>
</figcaption>
</figure>
<p>You don't have to be super smart to notice I am mixing hosted stuff
with remote stuff. I need a couple of iterations on this. I could throw
some HTML and that is it but for some reason I am using <a
href="https://gethomepage.dev/latest/" rel="noreferrer">homepage</a> for
this.</p>
<p>Also I have some concerns on storing secrets for homepage. The way it
is done is not kubernetes secrets friendly so I have to play a bit
here.</p>
<p>Anyway since it is not exposed to the internets I can live with it
for a while.</p>
<hr />
<h2 id="development-tools">Development tools</h2>
<h3 id="argocd">ArgoCD</h3>
<p>Since I have a k8s cluster I need to have something to deploy stuff
there. I am starting to have so many things that I need to keep
everything versioned in version control.</p>
<h3 id="forgejo">Forgejo</h3>
<p>I am moving some repos to Forgejo and using GitHub just for a backup.
I am using forgejo actions (which are mostly GitHub compatible by the
way) since I don’t need super complex workflows for anything.</p>
<h3 id="harbor">Harbor</h3>
<p>A simple docker image registry.</p>
<h3 id="longhorn">Longhorn</h3>
<p>For the important storage I've been using longhorn last months. It is
a very cool way of having storage redundancy in my K8S nodes.</p>
<p>Also the volumes in longhorn are the ones I consider critical so
backups are regularly made to a Backblaze b2 bucket.</p>
<h3 id="prometheus-and-grafana">Prometheus and Grafana</h3>
<p>One of the cool things of using Kubernetes is that you can use a
Grafana operator to host the Json of the dashboard of every service in
their own repo so you can compose the final picture based on the
dashboards you have stored in every application repository.</p>
<p>It is not like I have absolutely everything under control yet but it
is a small work in progress.</p>
<hr />
<h2 id="development-station">Development station</h2>
<figure class="kg-card kg-image-card">
<img src="https://www.trilliansphere.co.uk/images/iusearchbtw.jpg" class="kg-image" loading="lazy" width="603" height="532" alt="Trillian Sphere - artist and creative - Surreal artwork" />
</figure>
<p>I still keep my <a href="https://github.com/rafadc/dotfiles"
rel="noreferrer">dotfiles</a> alongside my <a
href="https://github.com/rafadc/dotfiles/tree/master/config/nvim"
rel="noreferrer">neovim config</a> so you can see here how I work. I use
<a href="https://github.com/rafadc/dotfiles/tree/master/config/xmonad"
rel="noreferrer">XMonad</a> in my main computer but I cannot consider
myself a power user.</p>
<p>I host this at my Forgejo but it is doubled down in Github so it is
easier to share with anyone.</p>
<hr />
<h2 id="media-consumption">Media consumption</h2>
<h3 id="plex">Plex</h3>
<p>A good old Plex. I could move to an Open source solution but I am too
lazy to relinquish on my lifetime license.</p>
<h3 id="audiobookshelf">Audiobookshelf</h3>
<p>My audiobook host. I don't use subscription services like Audible so
I have this so I can synchronize the position in the book from my phone
and the computer.</p>
<h3 id="miniflux">Miniflux</h3>
<p>My trustworthy RSS reader. I use it for blogs but it doubles down as
a notificator for new releases of software I use. If it is hosted on
Github or the like you can subscribe to the releases feed to know when
an update is posted.</p>
<p>Also I use its integration with telegram to have notifications on my
mobile phone when something changes.</p>
<h3 id="archivebox">ArchiveBox</h3>
<p><a href="https://archivebox.io/" rel="noreferrer">ArchiveBox</a> is
Pocket on steroids and self hosted. You send it URLs and he archives
them. Simple as that. It generates PDF files for everything which are
the view I often use.</p>
<h2 id="general-tooling">General tooling</h2>
<h3 id="plausible">Plausible</h3>
<p>I have a plausible instance for analytics for this blog. It is not
something extremely important but it is better than selling my visitor's
data to Google.</p>
<h3 id="ghost">Ghost</h3>
<p>This blog is hosted on Ghost. Since now I have a remote Proxmox I can
get small VMs for cheap so this sounds like a better way to ensure I
continue writing.</p>
<h3 id="klipper">Klipper</h3>
<p>To control my filament 3d printer I have a small <a
href="https://www.creality.com/products/creality-sonic-pad"
rel="noreferrer">Creality sonic pad</a>. This acts as a klipper server
that has a webcam connected so I can see what is going on in the
printer.</p>
<p>That is mostly it. It is a lot and it is overkill but it is extremely
fun.</p>
]]></description>
    </item>
    <item>
      <title>Moving to Ghost</title>
      <link>https://joy.pm/moving-to-ghost/</link>
      <guid isPermaLink="true">https://joy.pm/moving-to-ghost/</guid>
      <pubDate>Sat, 27 Apr 2024 18:32:41 +0000</pubDate>
      <description><![CDATA[<p>Last time I changed the backend for the blog was in 2016 when I moved
to an static site. It had all I needed. With RSS you have all you may
ever need.</p>
<p>Anyway times advance and things move on. Lately I am hosting <a
href="evilmeow.com" rel="noreferrer">evilmeow.com</a>, a Mastodon
instance and the whole idea of the Fediverse is extremely appealing to
me.</p>
<p>Being trapped in x.com should not be a thing and as of today it is.
You cannot have a realistic alternative to the amount of audience there
is in the old Twitter.</p>
<p>Federation has its own problems. There are defederation dramas and
really dubious instances out there. But at least there is a way to work
to solve those problems. It is true that there is not enough people yet
in the fediverse and a lot of people you want to follow are on x. Anyway
for me the pros outweight the cons. I am no celebrity so 🤷.</p>
<p>So when I saw this toot</p>
<figure class="kg-card kg-image-card">
<img src="./assets/image.png" class="kg-image" loading="lazy" width="894" height="903" />
</figure>
<p>I finally will have an alternative. I must admit there is stuff like
<a href="https://writefreely.org/" rel="noreferrer">Write Freely</a> but
they lack some features I want to have. Ghost is simple enough but
feature full enough so I can use it and <a
href="https://github.com/TryGhost/Ghost" rel="noreferrer">it is open
source</a> so...</p>
<p>It took me one afternoon to migrate in a dirty way. Mobile is broken
as hell today but I need another hour with the stylesheets to have
it.</p>
<p>Obviously my situation is just mine. Your case may differ but I am
happy to see that we have alternatives and I want to contribute to the
Fediverse being a better place. We are starting to have a lot of places
that we can federate</p>
<ul>
<li>My mastodon: <a href="https://evilmeow.com/@rafadc"
class="uri">https://evilmeow.com/@rafadc</a></li>
<li>My bookwyrm: <a href="https://bookwyrm.social/user/rafadc"
class="uri">https://bookwyrm.social/user/rafadc</a></li>
</ul>
<p>More alternatives will appear but the important thing about this is
that you create a ecosystem of interoperable tools with the things you
need. I am not too much into putting pictures of myself in the internet
but if I wanted I could always use <a
href="https://pixelfed.social/i/web/profile/513318589511660378"
class="uri">https://pixelfed.social/i/web/profile/513318589511660378</a>
and put more food pictures.</p>
<p>Every person is different and everyone will use different kind of
tools. It is important that they are interoperable and replaceable.
x.com, instagram and the like do all they can to retain you and not be
interoperable. The fact that you can migrate your mastodon account to
another instance and preserve everything you had is a killer feat every
social network should have.</p>
<p>I am looking forward to see what system I am using in another 8
years.</p>
]]></description>
    </item>
    <item>
      <title>The joy of books: Sprint</title>
      <link>https://joy.pm/the-joy-of-books-sprint/</link>
      <guid isPermaLink="true">https://joy.pm/the-joy-of-books-sprint/</guid>
      <pubDate>Thu, 25 Apr 2024 08:32:48 +0000</pubDate>
      <description><![CDATA[<p><a href="https://bookwyrm.social/book/133927/s/sprint">Sprint -
BookWyrm</a></p>
<p>Sprint is a book where I have mixed feelings. To be honest it is not
for me. I am happy anyway that it is being useful for a lot of people.
Its reviews are amazing so I am fine saying it is a good book.</p>
<p>Anyway, unfortunately I find extremely surprising people are finding
it so useful.</p>
<p>For some reason we need a book for someone to come and tell us: "For
Christ sake, you need to focus your full attention in the most important
thing until the problem becomes a matter of execution" or "structured
approaches are better than just throwing people at a problem" or "if no
one here can take a decision... what the hell are we doing here?".</p>
<p>It offers a decent taxonomy and framework for working in complex
problems but I find it too shallow.</p>
<figure class="kg-card kg-embed-card kg-card-hascaption">
<div class="iframe">
<div id="player">

</div>
<div class="player-unavailable">
<h1 id="se-ha-producido-un-error." class="message">
Se ha producido un error.
</h1>
<div class="submessage">
<p>No se puede ejecutar JavaScript.</p>
</div>
</div>
</div>
<figcaption>
<p>
<span style="white-space: pre-wrap;">Not this shallow but I had to do
this. Sorry.</span>
</p>
</figcaption>
</figure>
<p>I find far more appealing Cynefin taxonomy of problems and an
executive mindset. The rest is "just" organizational problems and we
have better frameworks for those.</p>
<p>I understand the way the book creates a framework that can be used
for consulting purposes. Also, I am pretty sure those consulting
services are super useful. Based on the reviews on the book a lot of
people need it and I am super super happy someone is solving those.
Anyway, as a society, why are we there still?</p>
<p>Why are we still questioning:</p>
<ul>
<li>Focus on solving the most important things</li>
<li>Focus is mandatory</li>
<li>Have input from all needed parts</li>
<li>Consider what is happening as part of a system. Use mental models to
approach those systems</li>
<li>All models are wrong. Some are useful.</li>
<li>Prototype early, fast and cheap</li>
</ul>
<p>If you think of it the book is a huge exercise of moving from the
proverbial Complex to Complicated</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://franciscocarcamo.com/wp-content/uploads/2022/01/Cynefin-Framework.png" class="kg-image" loading="lazy" width="640" height="446" alt="Modelo cynefin framework: ¿Qué es cynefin y cómo se aplica ..." />
<figcaption>
<span style="white-space: pre-wrap;">I can put Cynefin images for
hours</span>
</figcaption>
</figure>
<p>Maybe this is one of those cases where I find the book could have
been a long blogpost but looks like I am alone there still so it is good
someone took the time to do that.</p>
<p>In the book there are awesome examples of teams finding the solutions
to great problems with thinking out of the box... and whilst the
framework it is proposed is reasonable and necessary I don't think it is
the most important part on getting to those ideas.</p>
<p>This kind of processes "just" (again with quotes) removes obstacles
but I think they fall short on reproducing how those great ideas are
created and that is the part I am interested the most. There are some
small tips here and there on that but I'd love to see far more of that.
If we assume we have a healthy organization that knows where they are
going but they face a complex challenge you are already doing most of
the things here.</p>
<p>Also another critique is that the things that are hard to solve are
left unsolved. I have the feeling that it is focusing in organizations
that are just close to solving the problem but for internal stupidity
they don't do it.</p>
<p>I must assume most organizations are a mess. Bummer.</p>
<p>Anyway, again it is not that there is no bad advice there at all.
Please, follow most of the things in the book. Maybe I am looking for
solutions to stuff that no one can offer in a book.</p>
]]></description>
    </item>
    <item>
      <title>Projects</title>
      <link>https://joy.pm/projects/</link>
      <guid isPermaLink="true">https://joy.pm/projects/</guid>
      <pubDate>Tue, 23 Apr 2024 08:22:27 +0000</pubDate>
      <description><![CDATA[<p>An inventory of projects over time. I started keeping track of this
on early 2023.</p>
<h2 id="evilmeowcom">Evilmeow.com</h2>
<p><em>10/2022 - Active</em></p>
<figure class="kg-card kg-image-card">
<img src="./assets/image.png" class="kg-image" loading="lazy" width="1808" height="1097" />
</figure>
<p>My small mastodon instance. I don’t like the approach twitter is
taking althoug I still have to use it to keep in touch with some people.
Anyway I let my geekiness roam free here.</p>
<p>I may eventually open it to the public but it is something that won’t
happen anytime soon. I am reading a lot about content moderation and
probably it is not my cup of tea at the moment. The risk outweights the
benefit.</p>
<p>I don’t have the production code in k8s since I made a dead cheap
version to keep everything up and running keeping recurring cost under
20$ (for production alone).</p>
<p>Then the backups and monitoring are roaming free at my home. I am
monitoring everything using Grafana and Prometheus and I am relatively
happy with that part.</p>
<p>Keywords: ActivityPub, Ruby, K8s, Grafana, Prometheus</p>
<h2 id="micropomo">Micropomo</h2>
<p><em>02/2024 - Active</em></p>
<p><a href="https://github.com/rafadc/micropomo">Github</a></p>
<p>I needed a small pomodoro timer and I did not like anyone that was
already there so I built my own</p>
<p>Keywords: Golang, TUI</p>
<h1 id="no-longer-active-experiments">No longer active Experiments</h1>
<h2 id="software-crafters-madrid">Software crafters Madrid</h2>
<p><em>12/2012 - 03/2023</em></p>
<p>During 10 years I helped with the organization of a meetup in Madrid
about software development.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/meetup.png" class="kg-image" loading="lazy" width="782" height="136" />
</figure>
<p>This was a very interesting experience. I learnt a ton and hopefully
gave something back to all peers.</p>
<h2 id="the-library-of-joypm">The library of joy.pm</h2>
<p><em>02/2022 - 11/2023</em></p>
<p>This was a crazy experiment to track the books I am reading. It was
awesome to build a small rust tool I could use often.</p>
<p>You can find the notes for this in <a
href="__GHOST_URL__/post/2022-01-10-weird_software_joy_pm_library">this
blog post</a>.</p>
<p>Actually I don’t use this anymore because I use <a
href="https://bookwyrm.social/user/rafadc">bookwyrm</a> and Obsidian far
more.</p>
<h2 id="strato-or-tele">Strato or tele</h2>
<p><a
href="https://resonant-puffpuff-92c2bd.netlify.app">Website</a></p>
<p>This was a pet site to detect if a picture features a Stratocaster or
a Telecaster. It is documented <a
href="__GHOST_URL__/post/2023-05-25-a_bit_of_machine_learning/">in this
same blog</a>.</p>
<p>I am not proficient in ML at all so this was a starting point for me.
This was a fun thing to build.</p>
<p>It should still be working but I am not actively maintaining it.</p>
<p>Keywords: Machine learning, Python, HuggingFace</p>
]]></description>
    </item>
    <item>
      <title>Anemic Models</title>
      <link>https://joy.pm/anemic-models/</link>
      <guid isPermaLink="true">https://joy.pm/anemic-models/</guid>
      <pubDate>Fri, 16 Feb 2024 08:12:00 +0000</pubDate>
      <description><![CDATA[<p>Disclaimer: This is an article on Object Oriented programming. I
won’t intentionally talk about other models like functional. I won’t
commit suicide today. Sorry XD</p>
<p>When doing Domain Driven Design we give a lot of importance to the
modelling of the problem space we are working in. We should always do it
in OOP but for some reason DDD has managed to strike a nerve we weren’t
able to hit before.</p>
<p>But let’s start from the beginning. What is Object Oriented
Programming?</p>
<figure class="kg-card kg-image-card">
<img src="./assets/ooo.png" class="kg-image" loading="lazy" width="861" height="180" />
</figure>
<p>In OOO we have objects that combine both data and behaviours. And
they talk to each other in order to achieve a particular objective.</p>
<p>You can think that if you are using an object oriented programming
language you are already doing object oriented programming style. It is
not the case. There are a lot of situations that can cause not to be the
case. Today I will focus on one of them.</p>
<p>Allow me to use Ruby as a driver for the discussion. I will try to
keep it simple and understandable. I will make up also a “web framework”
to put the focus in the topic at hand. Also I will be ignoring all
dependency injection. I will maybe add a more complete example of
pushing functionality to models in another post.</p>
<p>Imagine we are building a Book catalogue system. Somewhere in our
system we have the following clases.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode ruby"><code class="sourceCode ruby"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="cf">class</span> <span class="dt">Book</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="ot">attr_accessor</span> <span class="wa">:name</span>, <span class="wa">:tags</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="cf">class</span> <span class="dt">Tag</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>  <span class="ot">attr_accessor</span> <span class="wa">:name</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span></code></pre></div>
<p>At some moment we are receiving a web request so we add a tag to a
book. We can have something like this</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode ruby"><code class="sourceCode ruby"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="cf">class</span> <span class="dt">TaggingController</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="cf">def</span> add_tag(params)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    book <span class="op">=</span> <span class="dt">Book</span><span class="at">.find</span>(params<span class="kw">[</span><span class="wa">:book_id</span><span class="kw">]</span>)</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    book<span class="at">.tags.append</span>(<span class="dt">Tag</span><span class="at">.new</span>(<span class="wa">name:</span> params<span class="kw">[</span><span class="wa">:name</span><span class="kw">]</span>))</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">BookRepository</span><span class="at">.save</span>(book)</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span></code></pre></div>
<p>Sometimes our objects stop being both data and state that talk to
other objects and become just data structures. The importance is only in
the things they contain.</p>
<p>We can take a bit of the behaviour and give it back to the objects
for example in something like this:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode ruby"><code class="sourceCode ruby"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="cf">class</span> <span class="dt">Book</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>  <span class="ot">attr_reader</span> <span class="wa">:name</span>, <span class="wa">:tags</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>  <span class="cf">def</span> apply(tag)</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    <span class="dv">self</span><span class="at">.tags.append!</span>(tag)</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="cf">class</span> <span class="dt">Tag</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>  <span class="ot">attr_reader</span> <span class="wa">:name</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="cf">class</span> <span class="dt">TaggingController</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>  <span class="cf">def</span> add_tag(params)</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>    book <span class="op">=</span> <span class="dt">Book</span><span class="at">.find</span>(params<span class="kw">[</span><span class="wa">:book_id</span><span class="kw">]</span>)</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>    book<span class="at">.apply</span>(<span class="dt">Tag</span><span class="at">.new</span>(<span class="wa">name:</span> params<span class="kw">[</span><span class="wa">:name</span><span class="kw">]</span>))</span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>    <span class="dt">BookRepository</span><span class="at">.save</span>(book)</span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span></code></pre></div>
<p>This is a subtle change, I know. I am trying to do the most minimal
example. But even if minimal we can see some interesting changes:</p>
<ul>
<li>We’ve put a name to the process of adding a tag to a book. We
decided to call that “apply”. Naming things is an important thing. This
name should be decided alongside domain experts and become part of your
ubiquitous language.</li>
<li>We have changed from <code>attr_accessor</code> to
<code>attr_reader</code>. We just constrained how a tag is applied to a
book. The objects expose all that information.</li>
<li>This is a bit contrived example since I want to keep is short but we
can discuss if we want a tagging service that is the one that actually
does that process. Normally I’d only have it if I consciously decide I
want to have a <a
href="https://martinfowler.com/eaaCatalog/transactionScript.html">Transaction
Script style</a></li>
</ul>
<p>If you compare the first objects to the second ones we can see that
we are having more information about the domain model of the application
too. They are not just bags of data. They are going from just data
structures to objects.</p>
<p>It is not strange to see the first, more procedural, code style. We
can start there as a mean to explore concepts and find good names for
the domain model but eventually, if we want to follow an object oriented
style we should move into the second direction.</p>
<p>Anyway, is this that bad?</p>
<p>Well, it is not like you are going to die in coding hell for this
stuff but I find some advantages to the non-anemic model. It encodes
more domain knowledge. Probably this is the most important thing for me.
It forces you to put names to more activities in the domain so you can
build an ubiquitous language for your context. That is often more
valuable than any technical benefit. It is an opportunity lost in this
context.</p>
<p>It is harder to model things this way, anyway. But that is not
necessarily a bad thing.</p>
<p>Normally when we code in this style we end up either putting a lot of
code in controllers or in <a
href="https://martinfowler.com/eaaCatalog/transactionScript.html">Transaction
Scripts</a>. While having code in controllers is clearly a bad practice
having transaction scripts may not be that much bad as a crime. It is
just that is somehow makes weird the choice of object oriented
programming for the problem.</p>
<p>If you are going full object oriented, let’s do it.</p>
<p>Happy hacking!</p>
<h3 id="resources">Resources</h3>
<ul>
<li><a
href="https://martinfowler.com/bliki/AnemicDomainModel.html">Martin
Fowler’s bliki on Anemic Model</a></li>
<li><a
href="https://martinfowler.com/eaaCatalog/serviceLayer.html">Martin
Fowler’s bliki on Service Layer</a></li>
<li><a
href="https://bookwyrm.social/book/502855/s/implementing-domain-driven-design">Implementing
Domain-Driven Design</a></li>
</ul>
]]></description>
    </item>
    <item>
      <title>A New Perspective on picking Books</title>
      <link>https://joy.pm/a-new-perspective-on-picking-books/</link>
      <guid isPermaLink="true">https://joy.pm/a-new-perspective-on-picking-books/</guid>
      <pubDate>Thu, 15 Feb 2024 08:11:00 +0000</pubDate>
      <description><![CDATA[<p>I’ve been lately studying a bit the contents of <a
href="https://www.gptandchill.ai/leetcode-for-ml">GPT &amp; Chill</a>.
Since machine learning is not something I am extremely proficient with I
am building my own library of resources to be able to query while going
through the contents. I am a big believer in reading the same concepts
from different sources to reinforce learning (no pun intended).</p>
<p>I am taking this time a more structured approach in choosing my
sources. The course is indeed a non-deep overview on purpose and it is
extremely practical. So I want to complement it with a couple of books.
I am trying anyway not to use the books people say are the best but
books that take a different approach on the same subject.</p>
<p>Take linear regression for example. I can read a very Python heavy
book like <a
href="https://learning.oreilly.com/library/view/hands-on-machine-learning/9781492032632/">Hands-On
Machine Learning with Scikit-learn, Keras and TensorFlow</a> that has a
hands on coding approach and I can complement it with a visual guide on
statistics like <a href="https://statquest.gumroad.com/l/wvtmc">The
StatQuest illustrated guide to machine learning</a> that approaches the
same problem in a different way.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/2024-02-15_14-50.png" class="kg-image" loading="lazy" width="726" height="1053" />
</figure>
<p>This causes the same content to be received in different ways. This
is not exactly multimodal learning but it is probably better than
repeating similar sources. I still have to mix audio, videos and social
interactions in the mix. Videos are already in the mix so I am going
through some audiobooks on statistics. I cannot find any meetups in the
place I am living now so I will try some online communities.</p>
<p>This may sound a bit weird but I still get super happy every time I
can sharpen the saw a little bit more.</p>
<p>Happy hacking!</p>
]]></description>
    </item>
    <item>
      <title>Software engineers, not programming language engineers</title>
      <link>https://joy.pm/software-engineers-not-programming-language-engineers/</link>
      <guid isPermaLink="true">https://joy.pm/software-engineers-not-programming-language-engineers/</guid>
      <pubDate>Mon, 12 Feb 2024 08:10:00 +0000</pubDate>
      <description><![CDATA[<p>This is probably the 1000 post on the subject but from time to time
it is fine to repeat the message.</p>
<p>During all my career I’ve been avoiding to be identified with any
technology. Instead, I’d love to be identified by the fact that I use
technology to solve problems.</p>
<p>This has a couple of implications that I’d love to enumerate</p>
<h2 id="programming-languages-will-change-with-time">Programming
languages will change with time</h2>
<p>I prefer not to be called a “Ruby engineer” for example. If I do
that, I am losing the opportunity to learn from different contexts. We
tend to stand in our echo chamber of our environment. We lose the
ability to see different approaches to the same kind of problems in
order to better understand the tradeoffs.</p>
<p>This is why I tend to use multiple stacks in <a
href="https://github.com/rafadc/micropomo">multiple</a> <a
href="https://github.com/rafadc/library.joy.pm">pets</a> that are not
the same stacks I use in my day to day job.</p>
<h2 id="you-can-go-wide-but-also-deep-in-some-things">You can go wide
but also deep in some things</h2>
<p>This does not mean you have to avoid going deep in some stuff. It
makes sense to become an expert in your text editor. It is just that you
should not despise the rest.</p>
<p>I’ve been using Emacs for a long time but last year moved to neovim.
It forces you to make a mental change that is well worth. I don’t regret
any second I spent tweaking my (emacs
config)[https://github.com/rafadc/emacs.d/blob/master/settings.org] and
it helped me a lot in creating a workflow I am happy with for vim.</p>
<h2
id="there-are-some-things-that-belong-to-a-common-knowledge-that-is-well-worth-the-effort">There
are some things that belong to a common knowledge that is well worth the
effort</h2>
<p>I am finishing another skim of <a
href="https://github.com/rafadc/emacs.d/blob/master/settings.org">Designing
data intensive applications</a> and its knowledge can be applied to an
enormous set of situations. It will not perish when new databases arrive
or will not fade out when a new feature appears in PosgreSQL. It can be
helpful when you are in a MySQL environment or in a MongoDB project.
That knowledge is extremely valuable.</p>
<h2 id="we-learn-by-connecting-the-dots">We learn by connecting the
dots</h2>
<p>This is often overlooked. There is one helpful strategy on learning
that consists in associative learning. You learn better by connecting
similar concepts together. Knowledge is better anchored if you have many
different places where you get the same concept from. For example, if
you know how to do concurrency in Go it will be far more easy for you to
grasp it in Javascript. But also the other way round it true. You will
become better and the concurrency you already knew by knowing what other
options are and what tradeoffs other people had.</p>
<p>Also, this way of accessing the same concepts from different angles
trains our ability to check to the same problem from different
perspectives.</p>
<p>So, my recommendation to all my fellow inhabitants of the tech
landscape is to prevent yourself from becoming a Java engineer and
instead become a problem solver that specializes in tech to solve
problems.</p>
<p>Happy hacking!</p>
]]></description>
    </item>
    <item>
      <title>Hello World</title>
      <link>https://joy.pm/hello-world/</link>
      <guid isPermaLink="true">https://joy.pm/hello-world/</guid>
      <pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>Welcome to the new site!</p>
<h2 id="whats-this">What’s this?</h2>
<p>A test post to verify the build works.</p>
]]></description>
    </item>
    <item>
      <title>Choosing my next read</title>
      <link>https://joy.pm/choosing-my-next-read/</link>
      <guid isPermaLink="true">https://joy.pm/choosing-my-next-read/</guid>
      <pubDate>Tue, 14 Nov 2023 08:09:00 +0000</pubDate>
      <description><![CDATA[<figure class="kg-card kg-image-card">
<img src="./assets/reddit-1.jpg" class="kg-image" loading="lazy" width="1049" height="296" alt="My CS book collection is on par with my Steam library" />
</figure>
<p>I’ve been there. We have more books that what we like to admit and we
have more games in Steam than what we’d like to admit. I was hoarding
content for three more lives as if I was to get them.</p>
<p>This small rant is my own way of admitting that time is limited.
Acknowledging that time is limited, anyway, has some implications.</p>
<p>Choosing your next read is a very rewarding task. You get the
dopamine shot of imagining yourself when you already know the topic of
the book. It feels great. Anyway when the time comes and you need to
actually invest the effort in doing the read… well then that makes the
thing harder. Reading is a high reward high effort activity in contrast
to shopping books which is a low effort, low reward activity.</p>
<p>At the end of the day we try to spend a lot of time in choosing a new
read since we are going to spend a lot of time reading so it may be
worth the effort, isn’t it?</p>
<p>Well… it is and it is not. I spend too much time in small bursts of
choosing a next read instead of actually reading. I could just
concentrate that effort in a single moment making my decission more
conscious and removing the background noise of all the other potential
choices I could have taken.</p>
<h2 id="hoard-and-just-choose-when-you-have-to">Hoard and just choose
when you have to</h2>
<figure class="kg-card kg-image-card">
<img src="./assets/hoarding.webp" class="kg-image" loading="lazy" width="512" height="666" />
</figure>
<p>I am applying the same principles I do with software decissions. I
delegate the decission to choose my future reading when to the moment I
have to. This has the same advantages than with software. The best
moment to decide what to read is when you have to. Your interests may
have changed by that time or you have more pressing endeavours to pursue
that make another read more appropriate.</p>
<p>You should either read stuff you can apply in your daily life or
change your daily life so you can apply stuff you read.</p>
<h2 id="invest-the-energy-in-making-reading-cheaper">Invest the energy
in making reading cheaper</h2>
<p>I am, lately, making reading accessible at all times. I store just a
list of potential reads. Something dirty in an obsidian note. I add and
remove things there with no thinking upfront. I just embellish the list
when I am actively working on it finding my next thing.</p>
<p>Also I am starting to favour audiobooks that I can listen to while
cooking or jogging. Something I can have at all times for short bursts
of time.</p>
<p><a href="https://www.youtube.com/watch?v=yfALZJcurZw">Listening to
this Ali Abdaal podcast</a> has inspired me to start increasing slowly
the speed which audio is played. I am not a native english speaker so I
started slow but I am increasing with time and it looks like there is
something deep there. It makes sense anyway. Listening requires less
brain cycles than talking.</p>
<h2 id="leave-reads-unfinished">Leave reads unfinished</h2>
<p>This is a classical advice I won’t elaborate here but I just will
repeat it instead. Leave books unfinished if you are getting nothing
from them. Do not suffer for nothing. Use your time wisely.</p>
<p>Happy hacking!</p>
]]></description>
    </item>
    <item>
      <title>Shutting down hackers.surf</title>
      <link>https://joy.pm/shutting-down-hackers-surf/</link>
      <guid isPermaLink="true">https://joy.pm/shutting-down-hackers-surf/</guid>
      <pubDate>Thu, 12 Oct 2023 08:08:00 +0000</pubDate>
      <description><![CDATA[<p>I will be shutting down hackers.surf Lemmy instance. I will be moving
my account to lemmy.world. Don’t get me wrong, anyway. Lemmy is a great
project that deserves to be kept and iterated. Anyway it is still
fundamentally different from Mastodon in the sense that hosting an
instance as a mere user makes less sense. Unless you are hosting a
community you lose a lot of the benefits of searching and I still find
myself too often into other people instances rather than my own.</p>
<p>I will put more effort into evilmeow.com and probably will be
thinking in opening it into the public with the extra headroom I get
from this.</p>
<p>Still, the idea of having public facing social services is scary to
me. The moderation part will be a challenge and I only have good will as
a weapon.</p>
]]></description>
    </item>
    <item>
      <title>A bit of machine learning with a Stratocaster</title>
      <link>https://joy.pm/a-bit-of-machine-learning-with-a-stratocaster/</link>
      <guid isPermaLink="true">https://joy.pm/a-bit-of-machine-learning-with-a-stratocaster/</guid>
      <pubDate>Thu, 25 May 2023 08:07:00 +0000</pubDate>
      <description><![CDATA[<p>I built <a href="https://resonant-puffpuff-92c2bd.netlify.app/">a
tiny site to detect if in a given picture there is a photo of a
stratocaster or a telecaster</a>.</p>
<p>Since we are in another AI surge I decided to write some pets in
relation to machine learning. I was experimenting a bit with image
categorization so I wrote one small web app. It is a super basic neural
net to categorize if a photo contains either a Telecaster or a
Stratocaster. Something that the world was not asking for but was really
needed.</p>
<p>I built a small sloppy React UI on top of it. Do not be hard. It is
my first React project.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/strato-or-tele.png" class="kg-image" loading="lazy" width="783" height="725" />
</figure>
<p>Anyway the focus was on the machine learning side of things. Some
learnings:</p>
<ul>
<li>Data cleaning is a PITA and takes a shitload of time. I spend a lot
of time categorizing images. I tried to do a script to download a couple
thousand from Duck duck go but at the end of the day I had to do a lot
of validation manually.</li>
<li>Iterating can get you to decent error rates. My first versions hace
30% error rate and I stopped at about 6 and taking into account that I
am just a beginner probably this can be done far far better.</li>
<li>Having a tough metric to fight like the error rate is super
motivating. You run your training and evaluate your model against the
testing set. It is super motivating to see how far you can go. It is far
more rewarding than the green red cycle of TDD.</li>
<li>I have a vast amount of things to learn. I am changing from resnet
to levit because the error is lower. Why? YOLO!!! XD</li>
</ul>
<p>It was easier than what I expected though. I am super happy to see
than in an afternoon I can hack something relatevely useful. Let’s
continue the journey!</p>
<p>Have fun!</p>
]]></description>
    </item>
    <item>
      <title>Oh my, oh my, AI</title>
      <link>https://joy.pm/oh-my-oh-my-ai/</link>
      <guid isPermaLink="true">https://joy.pm/oh-my-oh-my-ai/</guid>
      <pubDate>Wed, 17 May 2023 08:06:00 +0000</pubDate>
      <description><![CDATA[<p>There is a lot going on with the AI scene lately. The proliferation
of LLMs is causing a lot of hype in the industry. I am super bad at
predicting trends. We had this hype with a lot of innovations in the
past and some stuck and some didn’t.</p>
<p>The super obvious comparison is cryptos and the blockchain. We all
have this in our recent memory so the comparison is evident. Anyway
there are many differences. Blockchain was a nice technology looking for
a good application. Cryptos were cool but… to much incentive to do the
wrong thing.</p>
<p>I prefer to talk about two examples of the past instead. 3d printing
and the iPhone.</p>
<p>3d printing never lived up to the hype. We all were thinking of a
world with cheap goods accessible to everyone instantly and it never
materialized. I am not saying 3d printing is not cool. I have 2 printers
myself but it never lived up to the hype. It is a niche market at the
moment that is absurdly fun but it didn’t transform the lives of 100% of
people.</p>
<p>iPhone on the other hand stuck. It was an immediate revolution and
its expectations lived up to it up to the point where every telecom
company had to create their own mobile phone. Today, we cannot live
without our smartphone.</p>
<p>Where will LLM’s fall? God only knows to be honest.</p>
<p>Anyway IMHO there is also a super meta discussion about how society
is behaving about these changes that is worth talking about.</p>
<p>Companies are running like headless chicken to implement everything
on an LLM even if it does not make sense. The context is that of a
recession and investment is going to AI. To AI we all go! No one cares
about building great products. VC money is still more tempting.</p>
<p>We live in a society where a lot of people has a mechanism of
talking. Everyone wants an audience when few people want to really
convey a message. There are tons of press headlines along the lines of
“AI says it is going to kill all humans”. The fight for attention is
terrible where the fight for quality is not there.</p>
<p>The first applications of ChatGPT are spam based things. We have
people writing 38 books in Amazon in a month and another submitting PRs
to hundreds of open source projects with code that does not even work.
We couldn’t destroy the culture of pursuing overnight success.</p>
<p>We have now an AI generating the text for an email that will be
summarized by a different email at the other end and probably we didn’t
need neither the first nor the second. We are living a new tragedy of
the commons.</p>
]]></description>
    </item>
    <item>
      <title>Software rots</title>
      <link>https://joy.pm/software-rots/</link>
      <guid isPermaLink="true">https://joy.pm/software-rots/</guid>
      <pubDate>Fri, 27 May 2022 08:05:00 +0000</pubDate>
      <description><![CDATA[<p>I had this discussion many times. Some people argument often that
software are ones and zero and software doesn’t rot because of that. If
you don’t change a software it doesn’t change. It’s only bits.</p>
<p>Ok. I’m fine with that. Maybe my problem is with the definition of
what means for a software to rot. If I had to give a definition to what
I mean for software to rot I’d say:</p>
<p><em>The value a given piece of software provides changes over time.
Almost always, this change is for its value to decrease.</em></p>
<p>That is what I mean with “software rots”. There are many reasons for
that. Let me elaborate a bit more.</p>
<h2 id="software-around-you-changes">Software around you changes</h2>
<p>Of course the most obvious case is that the software you have around
changes. Let’s imagine we are building a software that downloads all
your Facebook posts into a single file. Maybe that is extremely valuable
to you. If Facebook API changes your software provides no value until
you adapt to that.</p>
<p>Ok, ok. Contrived example. I know your software does not connect to
Facebook so you are immune to that. Aren’t you?</p>
<p>For example in a previous job we used Redis. A lot. There was this
funny little vulnerability. A Redis exploit that was discovered. Until
we updated Redis our whole platform was vulnerable (in case somebody
found a way to access it, but you can never say 100% you are secure). At
that moment our software was less valuable for some hours. It is not a
Redis issue. All the supply chain you are using to provide you service
is a potential point of failure. Each day a failure is discovered you
are vulnerable to an attack there and your value decreases. But wasn’t
that vulnerability there all the time? Yes it was. But the fact that now
is public has decreased your value a lot.</p>
<p>Also, you may be hosted on a single cloud provider. You are reliying
on them to provide your value.</p>
<h2 id="people-change">People change</h2>
<p>There is this old notion of perceived value. Once your software is in
production it starts losing the shiny brand new thing label slowly. The
more time it passes users will start seeing its features as the new
statu quo and it will feel less and less valuable to them.</p>
<p>Also, you are not the one writing software. There are more people
providing brand new software that does something similar to what you
software does. In a world with only one spreadsheet software this is
extremely valuable but as time passes and when you have 50 different
pieces of software doing that you need to do more things to provide the
same value. It may be doing it faster, doing it cheaper or better but
staying with the same 0’s and 1’s that never change will not make your
software be equally valuable over time.</p>
<h2 id="is-this-always-so-hard">Is this always so hard?</h2>
<p>To be honest I think it sounds harder than it really is. It just
means that we need to strive to improve, slowly but steadily. It is true
that a piece of code will always be a piece of code but software is just
a means to an end.</p>
<p>Also, not all software loses value at the same rate. I’ve seen
services stay in production for years with no change at all. That is
awesome. We should design products for that, but we are not always so
lucky to hit the right spot.</p>
]]></description>
    </item>
    <item>
      <title>Weird software: library.joy.pm</title>
      <link>https://joy.pm/weird-software-library-joy-pm/</link>
      <guid isPermaLink="true">https://joy.pm/weird-software-library-joy-pm/</guid>
      <pubDate>Mon, 10 Jan 2022 08:04:00 +0000</pubDate>
      <description><![CDATA[<p>Why a simple notion database when you can make it 100 times more
complicated but more fun?</p>
<p>Ok, let’s be honest. This is overkill. This is too much. A simple
notion page is enough for this but… this is 10 times more fun.
Architecturally speaking this is an exercise in gratuitous
complexity.</p>
<p>The finished product is in <a
href="https://library.joy.pm">library.joy.pm</a>.</p>
<h2 id="what-is-libraryjoypm">What is library.joy.pm?</h2>
<p>I take notes when reading books. But I do it in a very disorganized
way. Sometimes I just scribble the pages (when it is a physical book)
and some others I take some notes in notion. I also have plenty of
markdown files scattered in many places. And don’t forget some emacs org
files. I think I have those too.</p>
<p>This is my small attempt to become a bit more organized. I want to
automate the boring parts and let me just do the thinking.</p>
<h2 id="just-markdown-files">Just markdown files</h2>
<p>Markdown files are absolutely awesome to keep notes. It is a simple
format, it looks relatively future proof and you can version it in git.
Indeed this same blog is written mostly using markdown and it works just
fine.</p>
<p>So let’s start there. Let’s just create a git repo and put markdown
files there.</p>
<p>I could have stopped there. <a
href="https://github.com/rafadc/library.joy.pm/blob/main/site/books/drive.md">Github
renders markdown just fine</a> and notes look great. You have a web
interface and plenty of accessibility. It is more than enough for some
geeky book notes.</p>
<h2 id="a-better-visualization">A better visualization</h2>
<p>Ok, let’s take another step just for the sake of it. Let’s build a
website so I can present all those markdown files in a decent way. I
could have used <a href="https://jekyllrb.com/">Jekyll</a> or <a
href="https://gohugo.io/">Hugo</a> but I wanted to try something new. I
like <a href="https://pandoc.org/">pandoc</a> a lot. Sombody that keeps
a huge Graphviz with 10000 intercrossed lines between file formats in
their homepage must be nice people.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/pandoc-homepage.png" class="kg-image" loading="lazy" width="800" height="524" />
</figure>
<p>Pandoc is a great tool to convert between formats. We can use it to
convert markdown to html.</p>
<pre class="shell"><code>pandoc -f drive.md -o drive.html</code></pre>
<p>That would convert the notes on Markdown to HTML. Simple and neat.
Pandoc supports templates too so you can just</p>
<pre class="shell"><code>pandoc -f drive.md -o drive.html --template layout.html</code></pre>
<p>And that will apply the layout.html template to the output of pandoc.
Let’s take a look how how templates look</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode html"><code class="sourceCode html"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;!DOCTYPE</span> html<span class="dt">&gt;</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;</span><span class="kw">html</span><span class="ot"> lang</span><span class="op">=</span><span class="st">&quot;en&quot;</span><span class="dt">&gt;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;</span><span class="kw">head</span><span class="dt">&gt;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;</span><span class="kw">meta</span><span class="ot"> charset</span><span class="op">=</span><span class="st">&quot;UTF-8&quot;</span><span class="dt">&gt;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;</span><span class="kw">meta</span><span class="ot"> http-equiv</span><span class="op">=</span><span class="st">&quot;X-UA-Compatible&quot;</span><span class="ot"> content</span><span class="op">=</span><span class="st">&quot;IE=edge&quot;</span><span class="dt">&gt;</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;</span><span class="kw">meta</span><span class="ot"> name</span><span class="op">=</span><span class="st">&quot;viewport&quot;</span><span class="ot"> content</span><span class="op">=</span><span class="st">&quot;width=device-width, initial-scale=1.0&quot;</span><span class="dt">&gt;</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;</span><span class="kw">link</span><span class="ot"> rel</span><span class="op">=</span><span class="st">&quot;stylesheet&quot;</span><span class="ot"> href</span><span class="op">=</span><span class="st">&quot;/style.css&quot;</span><span class="dt">&gt;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;</span><span class="kw">title</span><span class="dt">&gt;</span>$title$<span class="dt">&lt;/</span><span class="kw">title</span><span class="dt">&gt;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;/</span><span class="kw">head</span><span class="dt">&gt;</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;</span><span class="kw">body</span><span class="dt">&gt;</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;</span><span class="kw">div</span><span class="ot"> class</span><span class="op">=</span><span class="st">&quot;container&quot;</span><span class="dt">&gt;</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>        $body$</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&lt;/</span><span class="kw">div</span><span class="dt">&gt;</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;/</span><span class="kw">body</span><span class="dt">&gt;</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="dt">&lt;/</span><span class="kw">html</span><span class="dt">&gt;</span></span></code></pre></div>
<p>You have some template variables in the template. The body is
<code>$body$</code> and it will be substituted by the output of the
pandoc. Now we have a beautiful stylesheet so we can see something
decent.</p>
<p>Let’s apply a bit of magic pixie dust in form of a Makefile and we
have our first version of a notes website.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/drive-first-version.png" class="kg-image" loading="lazy" width="1328" height="519" />
</figure>
<p>This could be enough.</p>
<h2 id="it-would-be-great-if-we-had-the-cover-shown">It would be great
if we had the cover shown</h2>
<p>If you paid close attention to the template I shown before you’ll see
there is a <code>$title$</code> variable. Pandoc parses YAML front
matter in the markdown files to apply those variables to the template.
So we can:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="an">title:</span><span class="co"> Drive</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>This is a great book on motivation.</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">...</span><span class="co">]</span></span></code></pre></div>
<p>And pandoc will substitute the title variable with the value of the
title.</p>
<p>Also, I wanted to show the cover of the book. So I could add just
some more metadata there and use <a href="https://openlibrary.org/">The
Open Library</a> images to be shown there. According to their
documentation we can create a link with the following url:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode html"><code class="sourceCode html"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>https://covers.openlibrary.org/b/olid/$openlibrary_id$-L.jpg</span></code></pre></div>
<p>I could then just add</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="an">title:</span><span class="co"> Drive</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="an">openlibrary_id:</span><span class="co"> OL15016965W</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">...</span><span class="co">]</span></span></code></pre></div>
<p>And in the template use that HTML.</p>
<figure class="kg-card kg-image-card">
<img src="http://localhost:1313/posts/2022-01-09-weird_software_joy_pm_library/showing-book-final.png" class="kg-image" loading="lazy" width="1085" height="801" alt="How books look in final version" />
</figure>
<p>But that is a bit of a pain in the ass. I need to open the browser,
look for the book and find the code. That is a bit time consuming for
me. Also, that will make me consume bandwidth of the open library so I’d
prefer to be a good citizen and host the images myself.</p>
<p>This may be a good opportunity to use a bit of Rust to create a
command line tool so I can ask it to generate the template file for
me.</p>
<p>The Open Library API is extremely simple and it does not need
authentication so I can use it to get the cover and some metadata like
the author of the books. I will add those to the templates.</p>
<p>Also, I can use <a href="https://direnv.net/">direnv</a> to change
the PATH automatically when entering the code folder so I have the
latest debug version of the binary easily accessible.</p>
<p>It is just writing <code>post_template</code> adding a bit of <a
href="https://github.com/fdehau/tui-rs">Tui-rs</a> to the mix and I am
ready to go!</p>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="http://joy.pm/content/media/2024/04/rust-tool-demo_thumb.jpg" data-kg-custom-thumbnail>
<div class="kg-video-container">
<div class="kg-video-overlay">
<p><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjMuMTQgMTAuNjA4IDIuMjUzLjE2NEExLjU1OSAxLjU1OSAwIDAgMCAwIDEuNTU3djIwLjg4N2ExLjU1OCAxLjU1OCAwIDAgMCAyLjI1MyAxLjM5MkwyMy4xNCAxMy4zOTNhMS41NTcgMS41NTcgMCAwIDAgMC0yLjc4NVoiIC8+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvc3ZnPg==" /></p>
</div>
<div class="kg-video-player-container kg-video-hide">
<div class="kg-video-player">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTIzLjE0IDEwLjYwOCAyLjI1My4xNjRBMS41NTkgMS41NTkgMCAwIDAgMCAxLjU1N3YyMC44ODdhMS41NTggMS41NTggMCAwIDAgMi4yNTMgMS4zOTJMMjMuMTQgMTMuMzkzYTEuNTU3IDEuNTU3IDAgMCAwIDAtMi43ODVaIiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zdmc+" />
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHJlY3QgeD0iMyIgeT0iMSIgd2lkdGg9IjciIGhlaWdodD0iMjIiIHJ4PSIxLjUiIHJ5PSIxLjUiIC8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHJlY3QgeD0iMTQiIHk9IjEiIHdpZHRoPSI3IiBoZWlnaHQ9IjIyIiByeD0iMS41IiByeT0iMS41IiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zdmc+" />
<span class="kg-video-current-time">0:00</span>
<div class="kg-video-time">
<p>/<span class="kg-video-duration">0:54</span></p>
</div>
<p>1×
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE1LjE4OSAyLjAyMWE5LjcyOCA5LjcyOCAwIDAgMC03LjkyNCA0Ljg1LjI0OS4yNDkgMCAwIDEtLjIyMS4xMzNINS4yNWEzIDMgMCAwIDAtMyAzdjJhMyAzIDAgMCAwIDMgM2gxLjc5NGEuMjQ5LjI0OSAwIDAgMSAuMjIxLjEzMyA5LjczIDkuNzMgMCAwIDAgNy45MjQgNC44NWguMDZhMSAxIDAgMCAwIDEtMVYzLjAyYTEgMSAwIDAgMC0xLjA2LS45OThaIiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zdmc+" />
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2LjE3NyA0LjNhLjI0OC4yNDggMCAwIDAgLjA3My0uMTc2di0xLjFhMSAxIDAgMCAwLTEuMDYxLTEgOS43MjggOS43MjggMCAwIDAtNy45MjQgNC44NS4yNDkuMjQ5IDAgMCAxLS4yMjEuMTMzSDUuMjVhMyAzIDAgMCAwLTMgM3YyYTMgMyAwIDAgMCAzIDNoLjExNGEuMjUxLjI1MSAwIDAgMCAuMTc3LS4wNzNaTTIzLjcwNyAxLjcwNkExIDEgMCAwIDAgMjIuMjkzLjI5MmwtMjIgMjJhMSAxIDAgMCAwIDAgMS40MTRsLjAwOS4wMDlhMSAxIDAgMCAwIDEuNDA1LS4wMDlsNi42My02LjYzMUEuMjUxLjI1MSAwIDAgMSA4LjUxNSAxN2EuMjQ1LjI0NSAwIDAgMSAuMTc3LjA3NSAxMC4wODEgMTAuMDgxIDAgMCAwIDYuNSAyLjkyIDEgMSAwIDAgMCAxLjA2MS0xVjkuMjY2YS4yNDcuMjQ3IDAgMCAxIC4wNzMtLjE3NloiIC8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3N2Zz4=" /></p>
</div>
</div>
</div>
</figure>
<h2 id="metadata-as-your-database">Metadata as your database</h2>
<p>Ok, so there we are. We can take notes and create new book pages
easily. Anyway I have one last annoyance. I have to manually keep the
index page adding every new book there and keeping the order. I could
generate the index page automatically but then I can sort the new books
by a single criteria. Having a static page is a must for me so I have to
check other options. Let’s go overkill again.</p>
<p>I am accepting the metadata in markdown files as a the source of
truth but that does not mean it has to be the only one. Since I am using
a tool already to download the images and generate templates I can use
the same tool to maintain a secondary metadata database.</p>
<p>I can maintain a sqlite database and upload it to the static site.
Then I can use <a href="https://github.com/sql-js/sql.js/">a WASM
version of sqlite</a> to load that database straight on from the browser
and use it to generate the index page.</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>xhr <span class="op">=</span> <span class="kw">new</span> <span class="bu">XMLHttpRequest</span>()<span class="op">;</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>xhr<span class="op">.</span><span class="fu">open</span>(<span class="st">&quot;GET&quot;</span><span class="op">,</span> <span class="st">&quot;/metadata/books.sqlite&quot;</span><span class="op">,</span> <span class="kw">true</span>)<span class="op">;</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>xhr<span class="op">.</span><span class="at">responseType</span> <span class="op">=</span> <span class="st">&quot;arraybuffer&quot;</span><span class="op">;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>xhr<span class="op">.</span><span class="at">onload</span> <span class="op">=</span> <span class="kw">function</span>(e) {</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> database_data <span class="op">=</span> <span class="kw">new</span> <span class="bu">Uint8Array</span>(xhr<span class="op">.</span><span class="at">response</span>)<span class="op">;</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> db <span class="op">=</span> <span class="kw">new</span> SQL<span class="op">.</span><span class="fu">Database</span>(database_data)<span class="op">;</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> sqlstr <span class="op">=</span> <span class="st">&quot;SELECT title, slug, finished_at, last_updated_at FROM books;&quot;</span><span class="op">;</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> result <span class="op">=</span> db<span class="op">.</span><span class="fu">exec</span>(sqlstr<span class="op">,</span> {})<span class="op">;</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>    result[<span class="dv">0</span>]<span class="op">.</span><span class="at">values</span><span class="op">.</span><span class="fu">forEach</span>(addToTable)<span class="op">;</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a>}<span class="op">;</span></span></code></pre></div>
<p>And yes, I could just create the table straight away but this looks
absurdly more fun. Also it opens a lot of possibilities in order to
display the information in funnier ways in the future.</p>
<p>Then I am using <a href="https://datatables.net/">DataTables</a> to
display the index so it is still readable as I add books. Also, I get
for free the sorting and searching features.</p>
<h2 id="finishing-touches">Finishing touches</h2>
<p>Just some minor details. For example, this is the first site I use <a
href="https://github.com/electerious/Ackee">Ackee</a> to gather visit
statistics. It is self hosted and cookie-less so metrics are tracked in
a privacy sensitive way. Let’s see how it works. I love the fact that I
don’t have to set cookies for a web like this but I’d love also to have
statistics on the number of visitors. On paper this looks like a
reasonable solution.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/showing-book-final.png" class="kg-image" loading="lazy" width="1085" height="801" />
</figure>
<h2 id="what-now">What now?</h2>
<p>I will start migrating more notes there. At this moment I just have a
small PoC to check that everything is working properly. I could download
the images in parallel, I could have a way to perform a full sync, I can
show more info in the pages, I can add tags… This will have to be
iterated. But this is the good thing on writing your own software.</p>
<p>Also with use I will discover more bugs in it. For example now I can
easily my notes on a book. I have to take a look to that.</p>
<h2 id="why-weird-software">Why weird software?</h2>
<p>This software could have been a Notion page. And it would have worked
just fine. From time to time it is nice to relax and do some
overengineering just for fun. And this is what it is. An exercise in
having fun with your tools and your workflow. I will try to keep using
it because after writing this I am a lot more involved with it than with
a boring (but more effective) Notion page.</p>
<p>Also writing this software allows me to take some takeaways:</p>
<ul>
<li>TUI in Rust is easier than what I thought</li>
<li>Embedding SQL in a webpage with no backend is a nice tool when you
want to give users a lot of freedom to explore data</li>
<li>Automate all the things!</li>
<li><a href="https://openlibrary.org/">The Open Library</a> is a
beautiful project and it has a lot of potential. I ditched GoodReads
already.</li>
<li>I have no eye for design</li>
<li>I should read more</li>
</ul>
<p>I had loads of fun writing this. I hope you had fun reading this
too!</p>
<p>Happy hacking!</p>
]]></description>
    </item>
    <item>
      <title>The map is not the territory</title>
      <link>https://joy.pm/the-map-is-not-the-territory/</link>
      <guid isPermaLink="true">https://joy.pm/the-map-is-not-the-territory/</guid>
      <pubDate>Wed, 08 Sep 2021 08:03:00 +0000</pubDate>
      <description><![CDATA[<p>There is little software in the development community that raises so
much anger as Jira. It is not a bad piece of software, don’t get me
wrong, but we’ve seen so much misuse there that it is not hard to find
people that recall past battle scars when they confront it again.</p>
<p>I will focus in Jira for the sake of the storytelling but this is
applicable to every single project management software.</p>
<p>During my holidays I invested some time in a beautiful book by Shane
Parrish called “The Great Mental Models vol.1” [<a
href="http://localhost:1313/post/2020-09-08-the-map-is-not-the-territory/#cit1">1</a>].
One of the lessons we see in the book is “The map is not the
territory”.</p>
<h1
id="what-do-we-mean-by-the-map-is-not-the-territory-what-do-we-mean-by-e2809cthe-map-is-not-the-territorye2809d">What
do we mean by “The map is not the territory”?
{#what-do-we-mean-by-%E2%80%9Cthe-map-is-not-the-territory%E2%80%9D}</h1>
<h1 id="what-does-this-have-to-do-with-jira">What does this have to do
with Jira?</h1>
<p>At the end of the day we use Jira as a model with the real project.
What is there is not the real project but a projection of the real
project. It is a simplification over reality so we can do some
operations over it with more ease.</p>
<p>Sometimes we forget that we are working with a model and we apply
some characteristics of the real project to the model or even worse, the
other way round.</p>
<h2
id="forgetting-that-a-model-may-not-be-complete-and-still-look-good">Forgetting
that a model may not be complete and still look good</h2>
<p>Unfortunately the project needs to look good.</p>
<h2
id="having-more-people-working-on-the-model-and-too-little-on-the-real-project">Having
more people working on the model and too little on the real project</h2>
<p>This is as nuts as having more cartographers than seamen.</p>
<p>Sometimes we spend too many resources making the model look good
whilst the real project is an absolute mess.</p>
<p>Having a tidy model may give the false impression that the project is
being tidy. Since most external stakeholders just see the model they
won’t enter the complex world of the real project they will build their
impression based on the model. This is a simple, intentional or not,
form of deception.</p>
<h1 id="section"></h1>
<p>[1] <a
href="http://localhost:1313/post/2020-09-08-the-map-is-not-the-territory/">The
great mental models vol1</a></p>
<p>Have fun!!</p>
]]></description>
    </item>
    <item>
      <title>Predictability, estimations, nobel prizes and powerful people</title>
      <link>https://joy.pm/predictability-estimations-nobel-prizes-and-powerful-people/</link>
      <guid isPermaLink="true">https://joy.pm/predictability-estimations-nobel-prizes-and-powerful-people/</guid>
      <pubDate>Tue, 16 Mar 2021 08:02:00 +0000</pubDate>
      <description><![CDATA[<p>I’ve had this conversation so many times I have to turn this into a
blog post.</p>
<p>This is a controversial topic. I understand that some people need
estimations and I may not be an authority to rant about this but… this
is my personal space :-).</p>
<p>A lot of times when dealing with software projects we say we are
optimizing for predictability. We want to build projects in such a way
that we know beforehand how much it is going to cost. That may be a
noble outcome but I think optimizing for that is a rabbit hole that can
lead us to many problems that we didn’t consider in the beginning.</p>
<p>We are trying to fundamentally go to a model where teams are
predictable and we are doing so by providing teams with the adequate
processes and tools so they can estimate and do their jobs better. This
sounds reasonable. In my humble experience, it is not. Not only
unreasonable, it is toxic.</p>
<p>There is more than one fundamental problem here.</p>
<h2 id="turning-estimations-into-commitments-or-even-deadlines">Turning
estimations into commitments or even deadlines</h2>
<p>When you want to be predictable you are aiming to be able to say
“hey, this will be finished by this date”. Often, we do this by asking
the team when they think they will have something finished and
immediately assign that a certain degree of truth. No matter how hard we
try science goes against us. This is not a problem that only software
development teams have.</p>
<p>In 2002 <a
href="https://en.wikipedia.org/wiki/Daniel_Kahneman">Daniel Kahneman</a>
won a Nobel prize for “his groundbreaking work in applying psychological
insights to economic theory, particularly in the areas of judgment and
decision-making under uncertainty.”. This sounds a lot like estimations
to me. And his findings are totally against what we are doing in
software developmentworld. You can see an example in [<a
href="http://localhost:1313/post/2021-03-16-predictability-estimations-nobel-prizes-and-powerful-engineers/#cit1">1</a>].
Estimations should be more “outside in” driven by comparisons and
previous art than “inside out” by having engineers being in a
negotiation to get the smallest possible date.</p>
<p>I don’t know when we turned this Agile trend into having a team of
engineers agreeing on having this done by a date but we did. The more
software teams I talk about estimations the more I see business using
them as if they were kind of an informed guess instead of just a random
number.</p>
<p>Kahneman found something really groundbreaking. Often, the more we
know about a given task the more biased we are to underestimate it.
Indeed, most of the times people that estimate tasks by comparing to
other similar things without knowing the details get better estimations
than the experts in doing that task. Kanheman has a Nobel prize. We
don’t. Kahneman 1 - Software Engineers 0. Probably we should read less
Uncle Bob and more behavioral economics but that is the topic for
another blog post.</p>
<h2 id="then-are-estimations-worthless-hell-no">Then, are estimations
worthless? Hell, no!</h2>
<p>We should estimate. We have clear the concept that estimations should
be made. But I disagree in two important parts. Who should estimate and
what is the reason for estimating.</p>
<p>If you managed to read all this until now and you have learn a bit
from Kahneman’s story, having an outside view is not only important but
paramount in guessing when things will be finished. In my experience, in
most of the companies I’ve seen only developers take part of it and the
rest of the people in the room (if there is any) taking the “you are the
experts” kind of attitude.</p>
<p>Why do we estimate then? We want to know when a project will be
delivered. But if you agree on the Nobel-prize-evidence-based premise
(wink, wink) why are we just onboarding engineers on it? Indeed they are
the worst people to do it if you believe in the premise before. Why are
our estimation process based on letting the devs guess instead of doing
other practices?</p>
<p>What is worse. What do we do when things go south? You can change
three things: scope, date or resources. None of those can be changed by
the people you are making responsible of the dates.</p>
<p>Again let me use another fallacy of appealing to authority and cite
Ron Jeffries [<a
href="http://localhost:1313/post/2021-03-16-predictability-estimations-nobel-prizes-and-powerful-engineers/#cit2">2</a>]
“Management has the responsibility to set the overall goals, the
resources, and the date. The Product Owner, or the Customer, has the
responsibility for giving us the best combination of features so that we
meet as many of the goals as possible by the date. Development has the
responsibility to deliver a smooth sequence of fully integrated software
versions, at least monthly, containing an always-growing collection of
running, tested features, as specified by the Customer and by
Management.”.</p>
<p>Let me repeat. If someone is guilty of not making deadlines that is
management because reaching dates is his job. I don’t know when they
lost it.</p>
<p>It is always fun to try to explain your job to your grandmother when
you are a software developer but, ideally, the purpose of your job
should be simple and the goal of software developers should be “deliver
the best software you can”. That’s it. They shouldn’t juggle on “should
I add a one month buffer here so my job is safe?”. The management
layer’s goal is “deliver on date” and take actions on the items we
mentioned before if it looks like we are not reaching the date. We have
agile coaches that are supposed to help the team to introspect so they
don’t forget being a bit better each day. When did we change this?</p>
<h2 id="toxic-implications-in-being-predictable">Toxic implications in
being predictable</h2>
<p>We all want to keep our jobs. No one wants to look bad in public. If
we are forcing people into being better at estimating and praising
people when they get it right a lot of teams will start being
predictable. Probably not because people do this intentionally but we
will create a Darwinian process where people doing the wrong thing will
be empowered and said they are doing the right thing. And that is
terribly bad.</p>
<ul>
<li>Everyone will be adverse to risk so they will pursue worse outcomes
in lieu of being more predictable.</li>
<li>No one will automate. Why should we? If you do the same task over
and over again you are able to estimate better how long it will take.
You are able to create cookie cutter sized jobs instead of creating a
cookie cutter machine so that task is no longer a problem. Indeed, being
able to estimate reliably is often a sign of lack of innovation.</li>
<li>Underestimating is punished, overestimating is totally fine. This
forces everyone into leaning towards underestimating which creates the
weird situation where we say that we can have something done by a given
date we both lie conscious and unconsciously at the same time.</li>
</ul>
<p>And sometimes we ask ourselves: Why building software in some
enterprises is so hard? Mostly because they asked for it.</p>
<h2 id="what-is-the-model-i-am-advocating-for-here">What is the model I
am advocating for here?</h2>
<p>Let’s be constructive and propose alternatives.</p>
<p>Think of teams as a black box. We need to ensure they are doing the
best they can, whatever that is. We should be on top of them so they
deliver “their best job ever”. Quote: “A business leader’s job is to
create great teams that do amazing work on time. That’s it. That’s the
job of management.” [<a
href="http://localhost:1313/post/2021-03-16-predictability-estimations-nobel-prizes-and-powerful-engineers/#cit3">3</a>]
The management layer over the teams should be on top of the team (not
only the individuals but the team) and use all the tools at his disposal
to train them to perform the best they can. The team should forget about
dates and do their best. The management should juggle with dates, scope
and resources to reach deadlines.</p>
<p>The people management layer needs metrics and tools to help the team
find areas where to improve. Velocities, estimations, KPIs and such
things are tools the manager has at his disposal so everyone can decide
what to experiment on and check the metrics again to validate if those
experiments are worthy or not.</p>
<p>I can only see two reasons for a team to do estimations. First, to
serve as a way of discussing what needs to be done and align what the
end goal is in the heads of people and second so the team can calculate
velocity. If you do the first one you don’t even need to annotate the
results and if you do the second one turning things into a date should
be enough to detect you are doing it for the wrong reason :D.</p>
<h2
id="a-team-should-be-closer-to-a-sports-team-in-the-sense-of-pursuing-improvement">A
team should be closer to a sports team in the sense of pursuing
improvement</h2>
<p>A team should be closer to a soccer team than to a family [<a
href="http://localhost:1313/post/2021-03-16-predictability-estimations-nobel-prizes-and-powerful-engineers/#cit3">3</a>].
We should be creating teams pursuing excellence instead of pursuing just
getting the estimated date right. Estimations and in particular velocity
calculations is just one of many indicators a team may have to self
assess better.</p>
<p>If teams innovate and try to constantly improve they may have a panel
of metrics and indicators on themselves same as we have Grafana
dashboards for our software. It is fine if the team decides that
velocity is one of the metrics they want to have. And to calculate that
you need estimations.</p>
<p>So yes, sometimes we should estimate and it needs to make sense to
track those estimations but what is important is the continuous
improvement and not the dates we get from there.</p>
<h1 id="section"></h1>
<p>[1] Kahneman excerpt from thinking fast and slow: <a
href="https://www.mckinsey.com/business-functions/strategy-and-corporate-finance/our-insights/daniel-kahneman-beware-the-inside-view#"
class="uri">https://www.mckinsey.com/business-functions/strategy-and-corporate-finance/our-insights/daniel-kahneman-beware-the-inside-view#</a></p>
<h1 id="-1"></h1>
<p>[2] Ron Jeffries. The nature of software development <a
href="https://ronjeffries.com/xprog/articles/jatmakingthedate/"
class="uri">https://ronjeffries.com/xprog/articles/jatmakingthedate/</a></p>
<h1 id="-2"></h1>
<p>[3] Patty McCord. Powerful: Building a Culture of Freedom and
Responsibility</p>
<p>Have fun!!</p>
]]></description>
    </item>
    <item>
      <title>Internal developer platforms</title>
      <link>https://joy.pm/internal-developer-platforms/</link>
      <guid isPermaLink="true">https://joy.pm/internal-developer-platforms/</guid>
      <pubDate>Wed, 27 May 2020 08:01:00 +0000</pubDate>
      <description><![CDATA[<p>If you are here is because probably you’ve heard the trend. Every big
company that appreciates herself is creating an internal developer
platform (IDP, from now on).</p>
<figure class="kg-card kg-embed-card">
<div class="iframe">
<div id="app">

</div>
</div>
</figure>
<p>Thank god some companies are starting to notice that not every single
person in the company should be pushing features forward but some people
should definitely be thinking on how we improve our daily work. Often,
for some reason, that task fell into the agile coaches but IMHO that is
not enough. They can think about the human interactions and the personal
aspects of the job but there is some technical stuff we need to think
about.</p>
<figure class="kg-card kg-image-card">
<img src="https://imgs.xkcd.com/comics/is_it_worth_the_time.png" class="kg-image" loading="lazy" width="571" height="464" alt="xkcd comic about something being worth automating" />
</figure>
<p>When I see that comic strip I always think that in developer time
that may even be more because every automation you have scales
proportionally with the amount of people that benefit from the
automation.</p>
]]></description>
    </item>
    <item>
      <title>The joy of books: A philosophy of software design</title>
      <link>https://joy.pm/the-joy-of-books-a-philosophy-of-software-design/</link>
      <guid isPermaLink="true">https://joy.pm/the-joy-of-books-a-philosophy-of-software-design/</guid>
      <pubDate>Mon, 30 Dec 2019 08:00:00 +0000</pubDate>
      <description><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I've just finished "A Philosophy of Software Design" by John
Ousterhout just two days ago. It is a book on software design. I've been
reading a lot about the topic of complexity lately and this came as a
recommendation from <a href="https://twitter.com/leehambley">Lee
Hambley</a>. Good stuff to come.</p>
<h2 id="some-things-i-loved">Some things I loved</h2>
<ul>
<li><strong>We need to talk about design</strong>: We need to talk about
the interfaces we show in our code. We need to open the discussion to
that kind of stuff that for some reason we give for granted. We are not
formally trained in design but we do it all day whatever we want it or
not.</li>
<li><strong>His naming is really good</strong>: He describes some
concepts that are intuitive but provides a really good metaphor. The
shallow vs deep classes metaphor is extremely useful and I will use it
from now on.</li>
<li><strong>Care about interfaces</strong>: He spends a lot of time
carefully designing the interfaces between pieces of software.</li>
</ul>
<h2 id="the-things-i-didnt-like">The things I didn't like</h2>
<p>Part of the statements he is doing are extremely dangerous IMHO. It
is always good to challenge assumptions but to my experience some things
are plain wrong.</p>
<ul>
<li><strong>Comments and documentation are not the same thing</strong>:
I think he is mislead by the fact that all the languages he mentioned
implement them the same way. He is making no distinction between one and
the other and that is an unforgivable sin. Early documentation makes
sense. Early comments are wishful thinking and mines in the land field.
<a
href="https://hexdocs.pm/elixir/writing-documentation.html#documentation-code-comments">Elixir
is an example of this</a>. Since both are plain text we use the comment
syntax to avoid syntax parsing. Anyway both are conceptually different
stuff that just by chance have the same implementation. During the book
I found both concepts confused one with each other.</li>
<li><strong>Tests don't only encourage only features</strong>: In this
point I strongly disagree and I think he has a ton of misconceptions
here. First thing is that he only considers unit and integration tests.
Second is that I dont know how he looks for proof that his api is
usable. Third is that IMHO test are most valuable when refactoring or
when they are hard to write.</li>
<li><strong>He falls a lot in strawman arguments</strong>: This is
something that I find extremely common in technical books lately. Some
people white sentences like: "in general people do X". That is often a
smell. You should critizise practices done right. It doesn't make sense
to challenge something done wrong because it is obviously… wrong.</li>
<li><strong>A big block of code with comments is rarely more readable
than smaller methods</strong>: I like that he tried to challenge this
but TBH the discussion leads nowhere and the example he put is IMHO a
counterexample of the point he is making. At the moment we've accepted
in the industry <a
href="https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two">The
rule of seven plus minus two</a> what he is trying to achieve doesn't
make sense. For example:</li>
</ul>
<div class="sourceCode" id="cb1"><pre
class="sourceCode ruby"><code class="sourceCode ruby"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="cf">def</span> bussiness_process</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  mutate_elements</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  publish_results</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>  notify_client</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="at">private</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="cf">def</span> mutate_elements</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>  my_collection<span class="at">.each</span> <span class="cf">do</span> <span class="op">|</span>element<span class="op">|</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>    element<span class="at">.mutate_somehow</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="cf">def</span> publish_results</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Sevice</span><span class="at">.publish</span>(my_collection)</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="cf">def</span> notify_client</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Notificator</span><span class="at">.send</span>(client)</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span></code></pre></div>
<p>will always be simpler to understand than</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode ruby"><code class="sourceCode ruby"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="cf">def</span> bussiness_process</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="co"># Mutation of the processes</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  my_collection<span class="at">.each</span> <span class="cf">do</span> <span class="op">|</span>element<span class="op">|</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    element<span class="at">.mutate_somehow</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="co"># We need to send the data to the final storage</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Sevice</span><span class="at">.publish</span>(my_collection)</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>  <span class="co"># The notification is needed to fullfill ISO-god only knows what</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Notificator</span><span class="at">.send</span>(client)</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="cf">end</span></span></code></pre></div>
<p>It will be harder to understand by definition because we have several
abstraction levels in the same context. I wrote an intentionally short
method in both scenarios to try to be as beneficial with his point as
possible. I don't believe humans can understand two intertwined things
in the same environment. The advantage of the small methods scenario is
that we have the tool of scoping.</p>
<p>If you check the first example the explanation of business concepts
is up to the documentation (see my comment before). The method acts as
an index you navigate if and inly if (and this is extremely important)
you want to dig into the details. And in the second you have only one
potential way to see things. Everything. Even if I am reading source
code I want to be able to layer things to the level I need. The second
option is by definition less structured.</p>
<p>The small methods version is harder to write because it forces you to
decompose the code, understand every detail and take the reader by the
hand to explain what the heck was going on when you wrote that. The one
method scenario is just easier for the developer.</p>
<p>There may be a case where the second is better but couldn't think of
a single one that is not a extremely weird case that shouldn't be
considered at all a general principle.</p>
<ul>
<li><strong>He believes in love at first sight</strong>: Do two designs
and you will know which is better when you see it. He is constantly
taking decisions to be right. In my experience we are wrong half of the
times so we design for change. We follow our hunches and we do the best
we can.</li>
</ul>
<h2 id="in-sum">In sum</h2>
<p>When I went through my notes before writing this post I wanted to be
sure I was not bashing this book at all. I think it is worth every
second spent reading and all arguments I may agree or not are well
explained and carefully exposed. It is also a very much needed
discussion. I may disagree in some things but that doesn't mean is a bad
book at all. Indeed after reading it I want to dig deeper in several
topics related to software design.</p>
]]></description>
    </item>
    <item>
      <title>A Graphviz primer</title>
      <link>https://joy.pm/a-graphviz-primer/</link>
      <guid isPermaLink="true">https://joy.pm/a-graphviz-primer/</guid>
      <pubDate>Sun, 17 Sep 2017 07:59:00 +0000</pubDate>
      <description><![CDATA[<p>One of the tools I've always wanted in my tool belt was a graphing
tool for text files. Since I handle a lot of text that goes into
repositories it is always a bit of a pain to somehow to version control
diagrams. Today we will dive a bit into the world of Graphviz. A tool
that turns scripts written into the dot language into images. Those
scripts describe directed graphs. We can anyway emulate non directed
graphs if we need it.</p>
<p>This has been extremely useful for me in combination with Emacs but
we will do one step at a time and we will start with the command line to
move to more complex integrations.</p>
<h2 id="your-first-graph">Your first graph</h2>
<p>No more thinking. Just fire your text editor and add create a
file</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode dot"><code class="sourceCode dot"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">digraph</span> <span class="va">G</span> <span class="ot">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">my_start</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">one_branch</span><span class="ot">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">my_start</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">another_branch</span><span class="ot">;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ot">}</span></span></code></pre></div>
<p>Then turn it into a graph with the command line</p>
<pre class="shell"><code>dot -Tpng the_file_i_just_created.dot -o graph1.png</code></pre>
<p>That will take the dot file you just created and turn it into this
simple, but effective graph.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/first_graph.png" class="kg-image" loading="lazy" width="345" height="155" />
</figure>
<p>And we have a starting point. Maybe this doesn't look too impressive
but we can see some things about the syntax of the dot language. We can
also tweak the command line a bit to change the output format to pdf,
jpg or post script.</p>
<p>We are defining a series of nodes and the connections between them.
On the example above we created a simple chain but we can make it as
complex as we want.</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode dot"><code class="sourceCode dot"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">digraph</span> <span class="va">G</span> <span class="ot">{</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">step1</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">step2</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">step3</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">step4</span><span class="ot">;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">step2</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">maybe_error</span><span class="ot">;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="ot">}</span></span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/mode_complex_node_list.png" class="kg-image" loading="lazy" width="259" height="347" />
</figure>
<p>So as you can see in the first part Graphviz is smart enough to
arrange the nodes appropriately once you specify the nodes and its
relations. It will try to organize the nodes top down in the more
reasonable fashion.</p>
<h2 id="styling-your-graph">Styling your graph</h2>
<p>As a quick graph sketching tool this is turning to be awesome (if you
use Emacs you can go to the end of the post to see how you can get
extremely quick feedback) but if we want to put the graph somewhere we
need to improve a bit the styling.</p>
<p>To improve how our graphic looks we can dive into the attributes of
elements. For example we can specify the shape of a node by using the
shape attribute.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode dot"><code class="sourceCode dot"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">digraph</span> <span class="va">G</span> <span class="ot">{</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_1</span><span class="co"> </span><span class="ot">[</span><span class="at">shape</span><span class="ot">=</span><span class="va">box</span><span class="ot">];</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_2</span><span class="co"> </span><span class="ot">[</span><span class="at">shape</span><span class="ot">=</span><span class="va">circle</span><span class="ot">];</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_3</span><span class="co"> </span><span class="ot">[</span><span class="at">shape</span><span class="ot">=</span><span class="va">plaintext</span><span class="ot">];</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_4</span><span class="co"> </span><span class="ot">[</span><span class="at">shape</span><span class="ot">=</span><span class="va">polygon</span><span class="co">, </span><span class="at">sides</span><span class="ot">=</span><span class="dv">6</span><span class="ot">];</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_1</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">node_3</span><span class="ot">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="ot">}</span></span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/styling.png" class="kg-image" loading="lazy" width="343" height="203" />
</figure>
<p>As you can see we can specify attributes of a node putting them in
square brackets after specifying the node. But this is not limited to
nodes but relations as well.</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode dot"><code class="sourceCode dot"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">digraph</span> <span class="va">G</span> <span class="ot">{</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_1</span><span class="ot">;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_2</span><span class="ot">;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_1</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">node_2</span><span class="co"> </span><span class="ot">[</span><span class="at">style</span><span class="ot">=</span><span class="va">dotted</span><span class="ot">];</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="ot">}</span></span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/styling_relations.png" class="kg-image" loading="lazy" width="107" height="155" />
</figure>
<p>And obviously we can play with color:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode dot"><code class="sourceCode dot"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">digraph</span> <span class="va">G</span> <span class="ot">{</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_1</span><span class="co"> </span><span class="ot">[</span><span class="at">color</span><span class="ot">=</span><span class="va">blue</span><span class="ot">];</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">node_1</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">node_2</span><span class="co"> </span><span class="ot">[</span><span class="at">color</span><span class="ot">=</span><span class="va">red</span><span class="ot">];</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="ot">}</span></span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/color.png" class="kg-image" loading="lazy" width="107" height="155" />
</figure>
<p>And if you check the docs you can achieve even more complex
styles</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode dot"><code class="sourceCode dot"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">digraph</span> <span class="va">G</span> <span class="ot">{</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">default_node</span><span class="co"> </span><span class="ot">-&gt;</span><span class="co"> </span><span class="va">styled_node</span><span class="ot">;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co">  </span><span class="va">styled_node</span><span class="co"> </span><span class="ot">[</span><span class="at">style</span><span class="ot">=</span><span class="va">filled</span><span class="co">,</span><span class="at">color</span><span class="ot">=</span><span class="st">&quot;.7 .3 1.0&quot;</span><span class="ot">]</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="ot">}</span></span></code></pre></div>
<figure class="kg-card kg-image-card">
<img src="./assets/styled_node.png" class="kg-image" loading="lazy" width="161" height="155" />
</figure>
<p>But I will just cover the basics in this post. ;)</p>
<h2 id="org-mode">Org-mode</h2>
<p>I started using Graphviz just for this reason and I think this is
really where it excels. After reading the lesson before probably you
have enough knowledge to automate something similar for your favourite
text editor.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/emacs-digraph.gif" class="kg-image" loading="lazy" width="800" height="600" />
</figure>
<h3 id="using-graphviz-to-draw-images">Using Graphviz to draw
images</h3>
<p>For all you emacs users out there you can use org-mode to render your
diagrams. If you add them to a source block and <strong>C-c C-c</strong>
you will see the following</p>
<pre class="text"><code> #+BEGIN_SRC dot :file my_output_file.png :cmdline -Kdot -Tpng
 digraph G {
   my_start -&gt; one_branch;
   my_start -&gt; another_branch;
 }</code></pre>
<p>#+END_SRC</p>
<h3 id="automatically-displaying-images">Automatically displaying
images</h3>
<p>In my case the images were not being displayed until I did an
<strong>org-display-inline-images</strong>. We can add a hook for that
to be automatically added for us</p>
<pre class="elisp"><code>(defun my/fix-inline-images ()
  (when org-inline-image-overlays
    (org-redisplay-inline-images)))

(add-hook &#39;org-babel-after-execute-hook &#39;my/fix-inline-images)</code></pre>
<h3 id="yasnippet">Yasnippet</h3>
<p>I also found extremely useful to define a snippet to create a new
graph.</p>
<pre class="shell"><code># -*- mode: snippet -*-
# name: dot
# key: dot_
# --
#+BEGIN_SRC dot :file ${1:file} :cmdline -Kdot -Tpng
digraph ${2:name} {
  $0
}
#+END_SRC</code></pre>
<p>So I can quickly play with graphics in Emacs.</p>
<h2 id="scripting">Scripting</h2>
<p>Another funny thing you can do is programatically generate these
graphs.</p>
<p>I created <a href="https://github.com/rafadc/following_graph">a
sample script to create a graph of your followers on Twitter</a> that
may not be very useful but it can illustrate what we can achieve
generating this graphs programatically.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/graph.png" class="kg-image" loading="lazy" width="2000" height="2000" />
</figure>
<h2 id="conclusions">Conclusions</h2>
<p>This has proven extremely useful for me to quickly sketch out ideas.
It is great to have Graphviz to care for all the positioning of nodes
and just let you think on the actual connections about ideas.</p>
<p>This also opens the door for a lot of new integrations on emacs to
generate images. I will work a bot more on that.</p>
]]></description>
    </item>
    <item>
      <title>Playing with happiness on Twitter</title>
      <link>https://joy.pm/playing-with-happiness-on-twitter/</link>
      <guid isPermaLink="true">https://joy.pm/playing-with-happiness-on-twitter/</guid>
      <pubDate>Thu, 07 Sep 2017 07:58:00 +0000</pubDate>
      <description><![CDATA[<p>Let’s play a bit with Twitter data.</p>
<p>There is a Spanish saying that says “La felicidad va por barrios”
which is used like “every dog has its day” but close-to-literally
translates into “happiness goes to some neighbours”. Me and some friends
wanted to check if happiness in Twitter depends on your neighbourhood
too. Yes, I know it is a weak justification for writing software but…
¯_(ツ)_/¯</p>
<p>This is my first python code so don’t be hard on me.</p>
<h1 id="gathering-data">Gathering data</h1>
<p>The first thing we did was writing a small python script to fetch
data from Twitter.</p>
<p>The relevant lines are like the following</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>api <span class="op">=</span> twitter.Api(consumer_key<span class="op">=</span>environ.get(<span class="st">&quot;TWITTER_CONSUMER_API_KEY&quot;</span>),</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>                  consumer_secret<span class="op">=</span>environ.get(<span class="st">&quot;TWITTER_CONSUMER_API_SECRET&quot;</span>),</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>                  access_token_key<span class="op">=</span>environ.get(<span class="st">&quot;TWITTER_ACCESS_TOKEN&quot;</span>),</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>                  access_token_secret<span class="op">=</span>environ.get(<span class="st">&quot;TWITTER_ACCESS_TOKEN_SECRET&quot;</span>))</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> twit <span class="kw">in</span> api.GetStreamFilter(locations<span class="op">=</span>flat_locations, languages<span class="op">=</span>[<span class="st">&#39;en&#39;</span>]):</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> twit[<span class="st">&#39;coordinates&#39;</span>] :</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>        writer.writerow([</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>            twit[<span class="st">&#39;place&#39;</span>].get(<span class="st">&#39;name&#39;</span>) <span class="cf">if</span> twit.get(<span class="st">&#39;place&#39;</span>) <span class="op">!=</span> <span class="va">None</span> <span class="cf">else</span> <span class="st">&quot;&quot;</span>,</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>            twit[<span class="st">&quot;timestamp_ms&quot;</span>],</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>            twit[<span class="st">&quot;text&quot;</span>].replace(<span class="st">&#39;</span><span class="ch">\n</span><span class="st">&#39;</span>,<span class="st">&#39;&#39;</span>),</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>            twit[<span class="st">&#39;coordinates&#39;</span>][<span class="st">&#39;coordinates&#39;</span>][<span class="dv">0</span>],</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>            twit[<span class="st">&#39;coordinates&#39;</span>][<span class="st">&#39;coordinates&#39;</span>][<span class="dv">1</span>]</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>        ])</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>        logger.info(twit[<span class="st">&quot;text&quot;</span>])</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>        csvfile.flush()</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    <span class="cf">else</span>:</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>        logger.info(<span class="st">&#39;Discarding twit without coordinates&#39;</span>)</span></code></pre></div>
<p>I focused on twits in English for reasons we will see later. We will
be dumping this data into a CSV file. Even if it is not completely
optimal we will be flushing on every twit write since I want to be able
to download periodically the csv to play with it meanwhile I connect to
the stream.</p>
<p>Twitter stream allows you to filter by location and that is what I
did. I was studying Spain and Germany so I defined bounding boxes for
them. The bounding boxes are far from perfect but that is enough for the
moment. The relevant piece of code looks like:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> flatten_array(array):</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> [item <span class="cf">for</span> sublist <span class="kw">in</span> array <span class="cf">for</span> item <span class="kw">in</span> sublist]</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>LOCATIONS <span class="op">=</span> {</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;spain&#39;</span>: [<span class="st">&#39;-7.07&#39;</span>,<span class="st">&#39;36.61&#39;</span>,<span class="st">&#39;5.14&#39;</span>,<span class="st">&#39;42.66&#39;</span>,</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>              <span class="st">&#39;-9.36&#39;</span>,<span class="st">&#39;41.82&#39;</span>,<span class="st">&#39;-1.58&#39;</span>,<span class="st">&#39;43.97&#39;</span>],</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;germany&#39;</span>: [<span class="st">&#39;6.74&#39;</span>,<span class="st">&#39;51.80&#39;</span>,<span class="st">&#39;14.52&#39;</span>,<span class="st">&#39;54.76&#39;</span>,</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>                <span class="st">&#39;6.15&#39;</span>,<span class="st">&#39;50.86&#39;</span>,<span class="st">&#39;14.80&#39;</span>,<span class="st">&#39;51.80&#39;</span>,</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>                <span class="st">&#39;6.15&#39;</span>,<span class="st">&#39;49.48&#39;</span>,<span class="st">&#39;12.34&#39;</span>,<span class="st">&#39;50.86&#39;</span>,</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>                <span class="st">&#39;8.04&#39;</span>,<span class="st">&#39;47.50&#39;</span>,<span class="st">&#39;13.13&#39;</span>,<span class="st">&#39;49.48&#39;</span>],</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>flat_locations <span class="op">=</span> flatten_array(LOCATIONS)</span></code></pre></div>
<p>If you want to calculate your own bounding boxes you can use <a
href="http://boundingbox.klokantech.com/">this web</a>. The twitter API
needs the array to be flat. Since I wanted to keep my locations as
readable as I could I just create a function to flat the array and
passed that to the api call.</p>
<h1 id="plotting-the-data-to-a-map">Plotting the data to a map</h1>
<p>I was using <a
href="https://github.com/python-visualization/folium">Folium</a> to draw
a map of the data I got. I did that because it was super super simple
and I wanted something super super simple :D :D :D</p>
<p>Once I got my first map I started just platting markers on the
appropriate places.</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> csv</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> folium</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>map_osm <span class="op">=</span> folium.Map(location<span class="op">=</span>[<span class="dv">47</span>, <span class="dv">0</span>], zoom_start<span class="op">=</span><span class="dv">5</span>)</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="cf">with</span> <span class="bu">open</span>(<span class="st">&#39;data/twits.csv&#39;</span>, <span class="st">&#39;r&#39;</span>) <span class="im">as</span> csvfile:</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    twit_reader <span class="op">=</span> csv.reader(csvfile)</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> row <span class="kw">in</span> twit_reader:</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>        twit_text <span class="op">=</span> row[<span class="dv">2</span>]</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>        folium.Marker([<span class="bu">float</span>(row[<span class="dv">4</span>]), <span class="bu">float</span>(row[<span class="dv">3</span>])], popup<span class="op">=</span>twit_text).add_to(map_osm)</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>map_osm.save(<span class="st">&#39;./data/map.html&#39;</span>)</span></code></pre></div>
<p>So with this tiny magic spell we can show something already. For
example:</p>
<figure class="kg-card kg-image-card">
<img src="./assets/non_classified_map.png" class="kg-image" loading="lazy" width="1448" height="928" />
</figure>
<p>We can see here that the most populated cities in Spain get the most
twits as expected. Will they be happy or sad?</p>
<h1 id="sentiment-analysis">Sentiment analysis</h1>
<p>Since I am not very good at writing classifiers I will just use
scikit-learn as a quick solution to get our sentiment analysis in place.
We can create a linear classifier really easily.</p>
<p>I broke the classifier into a training script and another one to
apply the analysis since I will be changing my CSV often but I won’t
want to retrain the classifier every time.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>TEST_SIZE <span class="op">=</span> <span class="dv">10000</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>logging.basicConfig(level<span class="op">=</span>logging.DEBUG)</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>logger <span class="op">=</span> logging.getLogger(<span class="va">__name__</span>)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&#39;Grabbing data&#39;</span>)</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>training_df <span class="op">=</span> pd.read_csv(<span class="st">&#39;twit_sentiment_analysis/dataset/Sentiment Analysis Dataset.csv&#39;</span>, error_bad_lines<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>examples_size, _ <span class="op">=</span> training_df.shape</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>TRAINING_SET_SIZE <span class="op">=</span> examples_size <span class="op">-</span> TEST_SIZE</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="co"># Create feature vectors</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&#39;Training predictor. Grab some drinks. This may be long.&#39;</span>)</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>vectorizer <span class="op">=</span> TfidfVectorizer(min_df<span class="op">=</span><span class="dv">5</span>,</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>                             max_df <span class="op">=</span> <span class="fl">0.8</span>,</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>                             sublinear_tf<span class="op">=</span><span class="va">True</span>,</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>                             use_idf<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>classifier <span class="op">=</span> svm.LinearSVC()</span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a>t0 <span class="op">=</span> time.time()</span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a>train_data <span class="op">=</span> training_df.head(TRAINING_SET_SIZE)[<span class="st">&#39;SentimentText&#39;</span>]</span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>train_vectors <span class="op">=</span> vectorizer.fit_transform(train_data)</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>train_labels <span class="op">=</span> training_df.head(TRAINING_SET_SIZE)[<span class="st">&#39;Sentiment&#39;</span>].<span class="bu">map</span>({<span class="dv">0</span>: <span class="st">&#39;negative&#39;</span>, <span class="dv">1</span>: <span class="st">&#39;positive&#39;</span>})</span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>classifier.fit(train_vectors, train_labels)</span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>t1 <span class="op">=</span> time.time()</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&quot;Training took </span><span class="sc">%d</span><span class="st"> seconds&quot;</span>, t1<span class="op">-</span>t0)</span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&quot;Dumping vectorizer and classifier for future use&quot;</span>)</span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a>joblib.dump(vectorizer, <span class="st">&#39;data/vectorizer.pkl&#39;</span>)</span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a>joblib.dump(classifier, <span class="st">&#39;data/classifier.pkl&#39;</span>)</span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&quot;Benchmarking training&quot;</span>)</span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a>test_data <span class="op">=</span> training_df.tail(TEST_SIZE)[<span class="st">&#39;SentimentText&#39;</span>]</span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a>test_labels <span class="op">=</span> training_df.tail(TEST_SIZE)[<span class="st">&#39;Sentiment&#39;</span>].<span class="bu">map</span>({<span class="dv">0</span>: <span class="st">&#39;negative&#39;</span>, <span class="dv">1</span>: <span class="st">&#39;positive&#39;</span>})</span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a>test_vectors <span class="op">=</span> vectorizer.transform(test_data)</span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a>logger.info(classification_report(test_labels, classifier.predict(test_vectors)))</span></code></pre></div>
<p>To be honest I still need to tweak the vectorizer since I don’t fully
get all the parameters. I used the corpus at <a
href="http://thinknook.com/twitter-sentiment-analysis-training-corpus-dataset-2012-09-22/">Thinknook</a>
to train the classifier. At the end I got a 79% of prediction which is
not good at all but it is enough for me to have some fun. I will try to
improve this in a further blog post.</p>
<p>Once the classifier is trained we can use it with:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&#39;Loading model&#39;</span>)</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>classifier <span class="op">=</span> joblib.load(<span class="st">&#39;data/classifier.pkl&#39;</span>)</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>vectorizer <span class="op">=</span> joblib.load(<span class="st">&#39;data/vectorizer.pkl&#39;</span>)</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>logger.info(<span class="st">&#39;Starting prediction&#39;</span>)</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>twits_rd <span class="op">=</span> pd.read_csv(<span class="st">&#39;data/twits.csv&#39;</span>, names<span class="op">=</span>[<span class="st">&#39;city&#39;</span>, <span class="st">&#39;user_id&#39;</span>, <span class="st">&#39;text&#39;</span>, <span class="st">&#39;lat&#39;</span>, <span class="st">&#39;long&#39;</span>])</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>twits_data <span class="op">=</span> twits_rd[<span class="st">&#39;text&#39;</span>]</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>twits_vectors <span class="op">=</span> vectorizer.transform(twits_data)</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>prediction <span class="op">=</span> classifier.predict(twits_vectors)</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>twits_rd[<span class="st">&#39;sentiment&#39;</span>] <span class="op">=</span> prediction</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>twits_rd.to_csv(<span class="st">&#39;data/twits_classified.csv&#39;</span>, header<span class="op">=</span><span class="va">False</span>, index<span class="op">=</span><span class="va">False</span>)</span></code></pre></div>
<p>So using pandas we can easily generate another file
‘data/twits_classified.csv’ with an extra column containing either
positive or negative depending on the classification of the tweet.</p>
<h1 id="plotting-the-results">Plotting the results</h1>
<p>Now we have all our data in place. We just need to modify a little
bit the code block that does the plot:</p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> row <span class="kw">in</span> twit_reader:</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>    twit_text <span class="op">=</span> row[<span class="dv">2</span>]</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> row[<span class="dv">5</span>] <span class="op">==</span> <span class="st">&#39;positive&#39;</span>:</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>        color <span class="op">=</span> <span class="st">&#39;green&#39;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>        icon <span class="op">=</span> <span class="st">&#39;smile-o&#39;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>    <span class="cf">else</span>:</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>        color <span class="op">=</span> <span class="st">&#39;red&#39;</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>        icon <span class="op">=</span> <span class="st">&#39;frown-o&#39;</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>    folium.Marker([<span class="bu">float</span>(row[<span class="dv">4</span>]), <span class="bu">float</span>(row[<span class="dv">3</span>])], popup<span class="op">=</span>twit_text, icon<span class="op">=</span>folium.Icon(color<span class="op">=</span>color, icon<span class="op">=</span>icon, prefix<span class="op">=</span><span class="st">&#39;fa&#39;</span>)).add_to(map_osm)</span></code></pre></div>
<p>And there we are:</p>
<figure class="kg-card kg-image-card">
<img src="./assets/classified_map.png" class="kg-image" loading="lazy" width="1102" height="1274" />
</figure>
<p>Ready to take a look around to see happy people everywhere.</p>
<h1 id="conclusions">Conclusions</h1>
<p>With this obviously unfinished we can see some things</p>
<ul>
<li>People tweets more happy feelings than sad. That great news. Maybe
because now it is summer! Probably just displaying in a map won’t give a
clear idea on the proportion between happy and sad tweets in an area. I
will try a heat map in a following post.</li>
<li>People in Spain don’t tweet a lot in English :D :D :D but I will
analyze that in a different visualization.</li>
<li>Barcelona and Madrid are the places that send the most tweets in
English. Germany’s distribution is much much equally distributed.</li>
<li>After reading all the data I had to filter out a lot of automatic
tweets. There were some bots that tweeted the weather, a lot of Endomodo
tweets, some people just posting the location… Cleaning the data a bit
was not hard though.</li>
<li>I got no clue on both machine learning and data visualization. I
need to work a lot more on this. :D</li>
<li>I need to be a lot more tidy while exploring the data I retrieved.
Next time I will try different visualizations. I am learning though
:)</li>
<li>This was really fun exercise anyway. You should try on your
own!</li>
</ul>
<p>Have fun!!</p>
]]></description>
    </item>
    <item>
      <title>org-edit-special and editing code in org mode</title>
      <link>https://joy.pm/org-edit-special-and-editing-code-in-org-mode/</link>
      <guid isPermaLink="true">https://joy.pm/org-edit-special-and-editing-code-in-org-mode/</guid>
      <pubDate>Sat, 25 Feb 2017 07:57:00 +0000</pubDate>
      <description><![CDATA[<p>One of the new funny things I found in emacs is org-edit-special.</p>
<p>I keep <a
href="https://github.com/rafadc/emacs.d/blob/master/settings.org">my
config files on GitHub</a> in an org mode file.</p>
<p>Since I am in an org mode the snippets and indentation and all the
stuff are from org mode major mode which is a pain in the ass. For that
purpose: org-edit-special to the rescue!</p>
<p>If we have an org mode file like the following:</p>
<pre class="elisp"><code>* Homebrew

I&#39;ll add homebrewe&#39;d emacs packages to load path

#+BEGIN_SRC emacs-lisp
(let ((default-directory &quot;/usr/local/share/emacs/site-lisp/&quot;))
  (normal-top-level-add-subdirs-to-load-path))
#+END_SRC</code></pre>
<p>You can place the cursor inside the BEGIN-END block and press
<code>C-c '</code> to invoke org-edit-special. Then you will open a new
window where you can edit the code in the proper major mode with all
your awesome customizations and snippets in place.</p>
<p>Happy coding!</p>
]]></description>
    </item>
    <item>
      <title>Taking notes and coding</title>
      <link>https://joy.pm/taking-notes-and-coding/</link>
      <guid isPermaLink="true">https://joy.pm/taking-notes-and-coding/</guid>
      <pubDate>Sun, 18 Dec 2016 07:56:00 +0000</pubDate>
      <description><![CDATA[<p>I am not such a big fan of paper. I don’t know. Maybe it is just me
but I found it extremely inconvenient for taking notes on how code
should work. It is too far away for my development environment. They are
difficult to share, specially in a remote environment, easy to lose and
difficult to transform. Also on paper everything looks easy and good
;)</p>
<p>I have used several alternatives for different situations:</p>
<h2 id="writing-pseudocodish-code">Writing pseudocodish code</h2>
<p>I’ve seen a lot of people writing notes like:</p>
<p>{% highlight pseudocode %} Get all the customers Find the oldest
group Send a complicated email to them Find the youngest ones Send a
twitter message {% endhighlight %}</p>
<p>IMHO taking that as notes is absurd. How does that differ from
throwing out a method that reads</p>
<p>{% highlight ruby %} oldest_customers = Customers.oldest
send_complicated_email oldest_customers youngest_customers =
Customers.youngest send_twitter_message youngest_customers {%
endhighlight %}</p>
<p>Use the code as your notes. You are forcing yourself to make yourself
clear there. Then you just need to fill the gaps.</p>
<p>If you want to TDD you can then start with the tests that will prove
if that code you are trying to write makes sense or not.</p>
<h2 id="using-a-personalnotesorg-file">Using a personal_notes.org
file</h2>
<p>In my emacs I bound F7 to a personal_notes.org file in the root of
the git repo I’m currently in. That way I can quickly jump to a
sketchbook where I can plate bits of text that I want to keep for
whatever reason.</p>
<h2 id="using-artist-mode">Using artist mode</h2>
<p>If for some reason I need to draw some quick figures I can use artist
mode. It is not extremely elaborated but it makes the trick a lot of
times.</p>
<p>You can find an example of what I am talking about in <a
href="http://cinsk.github.io//emacs/emacs-artist.html">this
screencast</a>.</p>
<h2 id="using-org-capture">Using org-capture</h2>
<p><a href="http://orgmode.org/manual/Capture.html">Org capture</a>
makes extremely easy to set reminders of things you need to do in the
future. With a key binding I can save a small text regarding what I need
to do plus a link to the location where I saw the problem.</p>
<p>I keep different files for different purposes. One for features that
would be awesome to add, other for the code that should be refactored,
other for topics to raise in the retro and some smaller ones for ongoing
projects.</p>
<p>This allows me to just concentrate in the task at hand and leave the
rest for a better moment.</p>
<h2 id="just-not-writing-it-down">Just not writing it down</h2>
<p>Ok, you can do this on paper but this is a friendly reminder that you
don’t always need to write down everything. Just let some things go.
Storing all your ideas may be too much. Keeping track of notes has a
cost too that needs to be leveraged.</p>
]]></description>
    </item>
    <item>
      <title>Playing with text to speech in emacs (part 1)</title>
      <link>https://joy.pm/playing-with-text-to-speech-in-emacs-part-1/</link>
      <guid isPermaLink="true">https://joy.pm/playing-with-text-to-speech-in-emacs-part-1/</guid>
      <pubDate>Fri, 16 Dec 2016 07:55:00 +0000</pubDate>
      <description><![CDATA[<p>Amazon has just announced Polly and I wanted to give it a try. Of
course the first thing that came to my mind was: <a
href="https://github.com/skeeto/elfeed">elfeed</a>! Polly is not free
but 4$ per 24 hours seems reasonable in order to read a post from time
to time. Anyway let’s start there, get the ball rolling, and if it works
well maybe we can think in adding more backends.</p>
<p>So, let’s go to work!</p>
<p>I was trying the command line and it looked good enough for me. If
you have aws-cli tools installed it is enough a mere:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">aws</span> polly synthesize-speech <span class="dt">\</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="at">--output-format</span> mp3 <span class="dt">\</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="at">--voice-id</span> Joanna <span class="dt">\</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="at">--region</span> eu-west-1</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="ex">--text</span> <span class="st">&quot;We are about to have a lot of fun!&quot;</span> <span class="dt">\</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    hello.mp3</span></code></pre></div>
<p>to get an mp3 downloaded into your machine. With that, probably we
can have just enough.</p>
<p>Let’s go to emacs. I just added to my config the following
function:</p>
<pre class="elisp"><code>(defun my/elfeed-send-to-tts ()
  &quot;Send current article to a text to speech system&quot;
  (interactive)
  (let*
    ((html-to-read (elfeed-deref (elfeed-entry-content elfeed-show-entry)))
     (text-to-read (replace-regexp-in-string &quot;&lt;.*?&gt;&quot; &quot;&quot; html-to-read))
     (temp-input-file (make-temp-file &quot;elfeed-input-tts&quot;))
     (temp-output-file (make-temp-file &quot;elfeed-output-tts&quot; nil &quot;.mp3&quot;))
     (polly-command (concat &quot;aws polly synthesize-speech --region eu-west-1 --output-format mp3 --voice-id Joanna --text \&quot;$(&lt; &quot; temp-input-file &quot;)\&quot; &quot; temp-output-file )))
    (progn
      (write-region text-to-read nil temp-input-file)
      (shell-command polly-command)
      (shell-command (concat &quot;open -g &quot; temp-output-file)))))</code></pre>
<p>Elfeed retrieves the content in HTML so the first thing I have to do
is get the raw text</p>
<pre class="elisp"><code>((html-to-read (elfeed-deref (elfeed-entry-content elfeed-show-entry)))
 (text-to-read (replace-regexp-in-string &quot;&lt;.*?&gt;&quot; &quot;&quot; html-to-read))</code></pre>
<p>Then, in order to be easier to me to work with the command line I
just created a temporary file to store the text and a temporary file
that will hold the mp3 of the text</p>
<pre class="elisp"><code>(temp-input-file (make-temp-file &quot;elfeed-input-tts&quot;))
(temp-output-file (make-temp-file &quot;elfeed-output-tts&quot; nil &quot;.mp3&quot;))</code></pre>
<p>I still need to investigate if the output file is correctly disposed
since I will be overwriting it with the shell.</p>
<p>The rest is pretty obvious. We just prepare the command:</p>
<pre class="elisp"><code>(polly-command (concat &quot;aws polly synthesize-speech --region eu-west-1 --output-format mp3 --voice-id Joanna --text \&quot;$(&lt; &quot; temp-input-file &quot;)\&quot; &quot; temp-output-file )))</code></pre>
<p>then prepare the input and fire the command</p>
<pre class="elisp"><code>(write-region text-to-read nil temp-input-file)
(shell-command polly-command)</code></pre>
<p>I’m using a little bash trick</p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="va">$(</span><span class="op">&lt;</span> input_file<span class="va">)</span></span></code></pre></div>
<p>That will put the file contents in the command. Probably I will have
to escape the output somehow but this is my first approach. Let’s add
this to the TODO list and continue.</p>
<p>Finally to play it I will use another dirty trick. This will only
work in macOS at the moment.</p>
<pre class="elisp"><code>(shell-command (concat &quot;open -g &quot; temp-output-file))</code></pre>
<p>This will open the default mp3 player in the background and read the
text.</p>
<p>With this we have:</p>
<figure class="kg-card kg-embed-card">
<div class="iframe">
<div id="player">

</div>
<div class="player-unavailable">
<h1 id="se-ha-producido-un-error." class="message">
Se ha producido un error.
</h1>
<div class="submessage">
<p>No se puede ejecutar JavaScript.</p>
</div>
</div>
</div>
</figure>
<p>So at least we have something working. We have still some problems
with escaping the input text. Also I found that the command line has a
limitation of 1000 characters. We will deal with this problems in
further posts.</p>
]]></description>
    </item>
    <item>
      <title>Back to a static site</title>
      <link>https://joy.pm/back-to-a-static-site/</link>
      <guid isPermaLink="true">https://joy.pm/back-to-a-static-site/</guid>
      <pubDate>Thu, 15 Dec 2016 07:50:00 +0000</pubDate>
      <description><![CDATA[<p>I left <a href="http://medium.com">Medium</a> as a main host for my
site. Don’t get me wrong. It is a great platform. I cannot recommend it
enough and for a non hosted solution I think it is the best you can get.
This is one of those “it is not you, it is me” situations. I want to be
back to regular blogging. Something more hand made. Something I can do
from emacs. I am willing to sacrifice a bit more reach in exchange for a
bit more of “my personal place”.</p>
<p>So I will point the main page of my site to my <a
href="https://jekyllrb.com/">Jekyll</a> and that is it. It is pushed to
<a href="https://gitlab.com/rafadc/joy.pm">gitlab</a> where the CI
publishes it. I finally used gitlab since looks like the integrated Ci
will give me more freedom to try some nasty stuff.</p>
<p>(Anyway probably I will check the crossposting plugin so I will still
post to medium)</p>
]]></description>
    </item>
    <item>
      <title>Sending to pocket from elfeed</title>
      <link>https://joy.pm/sending-to-pocket-from-elfeed/</link>
      <guid isPermaLink="true">https://joy.pm/sending-to-pocket-from-elfeed/</guid>
      <pubDate>Thu, 18 Aug 2016 07:49:00 +0000</pubDate>
      <description><![CDATA[<p>I often use elfeed to go quickly through my RSS subscriptions. There
are some of them that I want to take more time to read.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/01.png" class="kg-image" loading="lazy" width="742" height="655" />
</figure>
<p>Yes, it is mostly text but it is super useful.</p>
<p>I send the entries that I need more time to read to pocket. I do that
because I can use pocket client in my mobile phone or in the desktop or
wherever the hell I am. I can also use any spare moment in the bus or
the airplane even without internet access.</p>
<p>Elfeed by default has nothing like a pocket integration but as you’ll
probably know emacs lisp is always here to help.</p>
<p>One of the less known pocket features is the possibility to send
links via email. We will make good use of it. Remember that you need to
send the email from the same email address you have in pocket.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/02.png" class="kg-image" loading="lazy" width="563" height="279" />
</figure>
<p>In order to send emails I am using Msmtp. My configuration for gmail
is something like:</p>
<pre><code>account default
host smtp.gmail.com
port 587
protocol smtp
auth on
from my.email@gmail.com
user my.email@gmail.com
password application.password
tls on
tls_nocertcheck</code></pre>
<p>Remember to create an application password for this. Never use your
main password ;)</p>
<p>Msmtp is a beautiful tool to send email. You can send an email from
the command line just with:</p>
<pre><code>echo &quot;Hey!!!!&quot; | msmtp recipient@mail.com</code></pre>
<p>That said let’s create the binding in elfeed config. If we check the
source for elfeed show we can see that we can retrieve the URL of the
current entry with:</p>
<pre class="elisp"><code>(elfeed-entry-link elfeed-show-entry)</code></pre>
<p>Piece of cake. Let’s then let’s define an interactive function to
send the email:</p>
<pre class="elisp"><code>(defun elfeed-send-to-pocket ()
  &quot;Send current article to pocket&quot;
  (interactive)
  (let
    ((url (elfeed-entry-link elfeed-show-entry)))
    (shell-command (concat  &quot;echo &#39;\\n\\n&quot; url &quot;&#39; | msmtp add@getpocket.com&quot;))
    (message &quot;Saved to pocket!&quot;)))</code></pre>
<p>I always add the final message to remember what is happening and
avoid having an impossible to understand message like “Shell command
successful” in the editor.</p>
<p>Now in my use-package for elfeed I only need to add the following in
the config section</p>
<pre class="elisp"><code>:config
  (bind-keys :map elfeed-show-mode-map
                  (&quot;x&quot; . elfeed-send-to-pocket))</code></pre>
<p>This way when in an article I can just press x and the article will
be saved to pocket.</p>
<p>Happy emacsing!</p>
]]></description>
    </item>
    <item>
      <title>When to rewrite</title>
      <link>https://joy.pm/when-to-rewrite/</link>
      <guid isPermaLink="true">https://joy.pm/when-to-rewrite/</guid>
      <pubDate>Mon, 13 Jun 2016 07:48:00 +0000</pubDate>
      <description><![CDATA[<p>We all heard it. Probably some of us lived it. That beautiful moment
when somebody asks: “why don’t we rewrite our full application?”</p>
<figure class="kg-card kg-image-card">
<img src="./assets/01-1.png" class="kg-image" loading="lazy" width="1020" height="680" />
</figure>
<h2 id="things-to-consider-before-starting">Things to consider before
starting</h2>
<p>I’ve always said that we humans have the beautiful power of learning
from other people’s experiences so let me tell you mine:</p>
<ul>
<li>You will always lose features. Nobody knows completely what the
application does up to the smallest detail. Nobody knows all the corner
cases. Nobody remembers all the small decisions taking meanwhile wrig
the application.</li>
<li>A rewrite always takes longer than expected. Different orders of
magnitude.</li>
<li>Seriously. A rewrite will seem to be endless.</li>
<li>A rewrite is a marathon, not a sprint. It’s chemotherapy, not some
pills.</li>
<li>The result may be better than what we had before in terms of speed,
usability and maintainability. The problem is that the result may not be
better and the customers may still prefer the old version.</li>
<li>The sum of the cost may be smaller than the cost of evolving but you
are not giving new value to the customers during the rewrite.</li>
<li>If you improve the original application you are making the rewrite
harder and maybe the rewrite becomes more expensive that adapting the
old product. You are moving the target meanwhile you rewrite.</li>
<li>If switching technologies you need all the team to be trained again.
All of it. Not only coders.</li>
</ul>
<h2 id="why-a-big-rewrite-looks-awesome">Why a big rewrite looks
awesome?</h2>
<ul>
<li>It puts a “fixed” date to the end of all your problems. It brings
hope. Anyway careful about short (4–5 months) schedules. When you have a
cancer you should not trust the shaman that says that it can be fixed
imposing his hands.</li>
<li>A rewrite has not to fight with hard truths because it is not in
production by definition. You are making an imaginary product fight vs a
real one.</li>
<li>New and exciting technologies! Often the technical team is keen on
the idea because it gives them the possibility of ditching the old
system with all its defects and play with new toys.</li>
</ul>
<h2 id="things-to-never-do">Things to NEVER do</h2>
<ul>
<li>Switching technologies without taking the team into consideration.
The team is going to maintain the product long-term wise. Make them part
of it.</li>
<li>Counter-strike rewrites: A terrorist comes, drops the bomb and then
runs away. Maybe he hides and defends it for a while but he will finally
gets the fuck out before everything blows up. Do NOT leave this to
something whose future is not at stake in the rewrite. Do NOT make an
external team alone to do the rewrite.</li>
<li>Forget about the parts that brought us money of the old system since
we are too focused on its defects.</li>
</ul>
<h2 id="when-you-should-make-a-rewrite">When you should make a
rewrite</h2>
<ul>
<li>When you are changing WHAT the application does. If the new app is
remarkably different it is perfectly fine to rethink and rewrite. It may
even be good idea freeing your hands from old restrictions.</li>
<li>When you have some constraint that forces you to rewrite. For
example, the programming language you are using will no longer be
supported or works only in a weird kind of hardware that you will no
longer have access to.</li>
<li>You have reached the technology limit. You are Facebook, or Google
or Google and Facebook together.</li>
<li>When you are fully committed to it. You need to allocate a huge
chunk of time just to do it. Be prepared to invest a ton of time and
money on it.</li>
</ul>
<h2 id="bibliography">Bibliography</h2>
<p>I leave this for the end because those are far better posts than
mine:</p>
<ul>
<li><a
href="http://www.joelonsoftware.com/articles/fog0000000069.html">Joel
Spolsky on why you should never rewrite</a></li>
<li><a
href="https://signalvnoise.com/posts/3856-the-big-rewrite-revisited">DHH
on their success rewriting</a></li>
<li><a
href="http://onstartups.com/tabid/3339/bid/97052/How-To-Survive-a-Ground-Up-Rewrite-Without-Losing-Your-Sanity.aspx">Dharmesh
Shah on rewrites</a></li>
<li><a href="http://bikeshed.fm/58">A chapter of the bike shed on
rewrites</a></li>
</ul>
]]></description>
    </item>
    <item>
      <title>Compiling emacs</title>
      <link>https://joy.pm/compiling-emacs-2/</link>
      <guid isPermaLink="true">https://joy.pm/compiling-emacs-2/</guid>
      <pubDate>Sat, 07 May 2016 07:46:00 +0000</pubDate>
      <description><![CDATA[<p>Let’s go bleeding edge!</p>
<p>We are going to compile the latest version of emacs.</p>
<p>Just:</p>
<pre><code>git clone git://git.savannah.gnu.org/emacs.git
./autogen.sh
./configure --with-ns
make bootstrap
make
make install</code></pre>
<p>I’m compiling the OsX version. In linux probably you will not need
the with-ns flag. After some minutes you own built Emacs.app will be in
the nextstep folder. The last “make install” does not actually install
anything but it just copies several things into the Emacs.app so it is
ready to work. That step deceived me a lot because I lost some time
trying to make the thing work due to me thing that would install
something.</p>
<p>It will take a huge amount of time but that is life.</p>
<p>The last 25 version (at the moment of writing 24 is in homebrew) is
blazingly fast so probably I will try to stick to this for some
time.</p>
<p>There are some things that are a bit unintuitive. You can directly
alias emacs and emacsclient commands. I had to create two shell scripts
in my path like</p>
<pre><code>#!/bin/sh
/Applications/Emacs.app/Contents/MacOS/Emacs “$@”</code></pre>
<p>Now you can have at last a working emacsclient -nw. Happy
compiling!</p>
]]></description>
    </item>
    <item>
      <title>The black art of commit messages</title>
      <link>https://joy.pm/the-black-art-of-commit-messages/</link>
      <guid isPermaLink="true">https://joy.pm/the-black-art-of-commit-messages/</guid>
      <pubDate>Tue, 05 Apr 2016 07:42:00 +0000</pubDate>
      <description><![CDATA[<p>Good commit messages are the unicorn of the person maintaining legacy
code. I will try to review some commit messages I’ve found over the
years with its pros and cons.</p>
<p>I’ll break this into two categories: solving bugs and adding
features.</p>
<h2 id="solving-bug-commit-messages">Solving bug commit messages</h2>
<p>The brief</p>
<pre><code>Fixing code</code></pre>
<p><strong>Pros</strong>: At least it is not destroying code
<strong>Cons</strong>: What the hell is happening there? I can commit
anything under that excuse</p>
<p>More specific brief</p>
<pre><code>Fixing bug</code></pre>
<p><strong>Pros</strong>: At least it is not introducing bug.
<strong>Cons</strong>: What the hell is happening there?</p>
<pre><code>Making it work again</code></pre>
<p><strong>Pros</strong>: It is working… again <strong>Cons</strong>:
Why wasn’t it working before?</p>
<pre><code>Fix bug in module X</code></pre>
<p><strong>Pros</strong>: Module X has one less bug
<strong>Cons</strong>: At least I have a bit more context. Too bad I
could deduct that information from the diff.</p>
<pre><code>Fix #23344</code></pre>
<p><strong>Pros</strong>: I have a ton more context of the reason behind
the error. Unfortunately I don’t have it immediately. If I use a modern
issue tracking system I can navigate to a discussion regarding this bug
from my browser or editor. <strong>Cons</strong>: I have to go to my
issue tracking system</p>
<pre><code>Fix #23344 Removing n+1 problem</code></pre>
<p>The problem was that these lines were causing a nasty n+1 problem
because we have a lot of entries in our “users” table.</p>
<p><strong>Pros</strong>: I immediately have all the context of why the
change was made and what the guy that had written this piece of code
thought he was doing. <strong>Cons</strong>: A bit verbose sometimes</p>
<h2 id="adding-feature-commit-messages">Adding feature commit
messages</h2>
<pre><code>test / testing / just testing</code></pre>
<p><strong>Pros</strong>: The code is probably tested
<strong>Cons</strong>: Everything else</p>
<pre><code>Renaming method</code></pre>
<p><strong>Pros</strong>: Probably the new name is better
<strong>Cons</strong>: I saw it in the diff already. Why it is a better
name?</p>
<pre><code>Refactor</code></pre>
<p><strong>Pros</strong>: No behaviour was changed.
<strong>Cons</strong>: Just kidding. Please, tell me why you needed to
refactor this.</p>
<pre><code>Applying observer pattern</code></pre>
<p><strong>Pros</strong>: You know at least one design pattern.
<strong>Cons</strong>: Why is that a good idea in that scenario?</p>
<pre><code>Added new stories notification

This feature was requested in #112233

We are adding two new methods in the API so we can integrate in the future easier in our mobile app. These methods are now used in the frontend only.</code></pre>
<p><strong>Pros</strong>: A lot. <strong>Cons</strong>: Verbose</p>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>When I look into a commit message, often, I am desperately looking
for the reason why the change was made. I don’t want to know what
changed. There is a diffing tool for that. It does not hurt if it is
briefly describe what is done in the commit message but the most
important part is why the change was made. When I read a commit message
it often means that I am investigating a problem in the code so I need
to know your assumptions to see if you were wrong or the assumption is
now not valid.</p>
<p>Have some empathy with a person investigating something wrong with
your work, most probably, your future self.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/01-3.png" class="kg-image" loading="lazy" width="1600" height="1116" />
</figure>
<p>With the diff I can already see what changes were made. The text just
explains what the hell I was trying to do and more importantly why. This
gives the investigator a great deal of information to know if I was
wrong in case something needs to be fixed.</p>
<p>This is one of the reasons I am starting to avoid using the -m switch
of git commit</p>
<pre><code>git commit -m “I did something with the code”</code></pre>
<p>If we commit like that we are encouraging small commit messages and
it becomes too tempting not to explain a little bit to the next
programmer.</p>
<p>That is, also, why these days I’m embracing the idea of interactive
rebasing my branches before merging to the main branch. This gives me
some introspection time to organize the commits appropriately and reword
some commit messages to make the lives of future readers better.</p>
<p>Now probably a ton of people will come with better ideas than this
for commit messages. New ideas are welcome.</p>
]]></description>
    </item>
    <item>
      <title>Ambition</title>
      <link>https://joy.pm/ambition/</link>
      <guid isPermaLink="true">https://joy.pm/ambition/</guid>
      <pubDate>Fri, 01 Apr 2016 07:38:00 +0000</pubDate>
      <description><![CDATA[<p>Now that we are hiring at Platform161 I spent some time thinking
about values and culture at the workplace. There is one term that, in my
opinion, is completely misunderstood. Ambition.</p>
<p><strong>Warning</strong>: All the things I am about to write are my
opinions and of course they are highly opinionated.</p>
<p>We had a discussion because of a candidate that came here saying that
he aspired to manage a team of people in 5 years. In my opinion that is
not what I’d like to see. Why? That is not the kind of ambition I want
to see. That is climbing the ladder.</p>
<h3 id="the-question">The question</h3>
<p><em>What are your goals? What do you want/like to do?</em></p>
<p>I want to know what drives a candidate. What he likes to do with his
life. I want to ensure that I am able to provide and he is aligned with
us.</p>
<p><em>Not so good answer</em></p>
<p>I want to follow the career plan. I want to manage 5 people in X
years.</p>
<p>You being in a position of having people under your responsibility is
not a goal. Is a consequence. Also I’m hiring somebody whose goal is not
what he is going to do. His efforts will be focused on switching
positions instead of getting better.</p>
<p><em>Better answer</em></p>
<p>I’d like to learn X,Y and Z. I’d like to do A, B and C. All those
things are supposed to be done here in the position I’m getting in.</p>
<p>I am able to say if am am able to make you reach your goals. I am
able to see if you will be happy here. I am able to see if the company
will get profit for you achieving your goals.</p>
<p>This is the ambition I love. You want to become better. It won’t be
you against your peers. It will be you against yourself.</p>
<p>For me, finding a person that wants to become a manager is a warning
point. Really. The best managers I’ve ever met didn’t want to become
managers. Let me write it in a bigger font.</p>
<h2
id="the-best-managers-ive-ever-met-didnt-want-to-become-managers.-the-best-managers-ie28099ve-ever-met-didne28099t-want-to-become-managers">The
best managers I’ve ever met didn’t want to become managers.
{#the-best-managers-i%E2%80%99ve-ever-met-didn%E2%80%99t-want-to-become-managers}</h2>
<p>If you are responsible for a team the worst thing you can do is
become different. The kind of management that always worked for me is
the kind of role model you can identify with. As a “managee” you want to
somehow become that person. You don’t want that person’s position. He
wants the same kind of things you want. You are all in the same
boat.</p>
<p>If a software developer joins the team I work in I want him to become
a better developer. That is what I aspire too and we all can go in the
same direction. We can help each other.</p>
<p>There is some funny thing about the wrong kind of ambition. I think
that deep inside it is only that they want a better salary disguised in
wanting a different position. There is nothing wrong with ambitioning a
better salary. But why don’t people want a better salary by being more
valuable instead of being in a more expensive position? Why don’t you
start in the kind of work you want to do?</p>
<p>Maybe all this is because I am a software developer and I deeply love
my profession. I can’t see myself doing a different thing. Maybe it is
because of that that I like to be in a corporate culture where we all go
together to the same goal.</p>
]]></description>
    </item>
    <item>
      <title>A warm welcome to MS Sculpt keyboard</title>
      <link>https://joy.pm/a-warm-welcome-to-ms-sculpt-keyboard/</link>
      <guid isPermaLink="true">https://joy.pm/a-warm-welcome-to-ms-sculpt-keyboard/</guid>
      <pubDate>Tue, 21 Jul 2015 07:36:00 +0000</pubDate>
      <description><![CDATA[<p>I just moved to a new Microsoft Sculpt keyboard. I am more than happy
with it. I wanted to move to a split keyboard for a long time but I
never had the guts to try. Indeed I wanted to move to a Model 01 but
this makes a good intermediate step.</p>
<p>This is the first Microsoft product that I buy that feels really
beautiful. It is really nice to have it in your table.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/01.jpeg" class="kg-image" loading="lazy" width="2000" height="1161" />
</figure>
<p>The position of the hands is really comfortable although a bit odd
because it makes your wrists to stay horizontal. It feels strange in the
beginning but the position is super relaxed.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/02.jpeg" class="kg-image" loading="lazy" width="1600" height="634" />
</figure>
<p>There is a small detail I also love and it is that the spacebar is
broken into two. Probably this is a really small detail but if I press
it with a thumb the other thumb is not affected.</p>
<p>The keyboard is not bluetooth but comes with a small USB 2.4Ghz
adapter which may not be ideal but is nice enough.</p>
<p>I don’t like noisy keycaps and in this laptop they are really silent.
The pulsation is a bit mushy but if you come from a Apple keyboard like
me this will feel super fine.</p>
<p>I was able to customize it to my taste using Karabiner which is an
awesome tool for OsX. Also it allowed me to do some small hacks.</p>
<h2 id="remap-capslock-to-control">Remap CapsLock to control</h2>
<p>Of course I do this to every new computer I get. This saves a lot of
pinky emacs pain.</p>
<h2 id="switch-between-alt-and-command">Switch between Alt and
Command</h2>
<p>In the sculpt keyboard these two keys are reversed in relation to
where they are in a regular mac keyboard.</p>
<h2 id="use-ctrlijkl-for-cursor">Use CTRL+IJKL for cursor</h2>
<p>This is really awesome. I’m really enjoying this. Now I no longer
have to move my hands to the arrow keys for moving the cursor. I find
this also saner than Vim’s hjkl. I did this because in my opinion the
cursor keys in the sculpt are really uncomfortable to reach and now I
did this change also in my laptop’s integrated keyboard.</p>
<figure class="kg-card kg-image-card">
<img src="./assets/03.jpeg" class="kg-image" loading="lazy" width="1600" height="1200" />
</figure>
<p>In conclusion, I’m super happy with my acquisition. This is a really
nice addon to my setup. I’m looking forward to get really used to it and
next year I will aim for a Model 01.</p>
]]></description>
    </item>
    <item>
      <title>Pairing over Tmux</title>
      <link>https://joy.pm/untitled/</link>
      <guid isPermaLink="true">https://joy.pm/untitled/</guid>
      <pubDate>Sat, 11 Jul 2015 07:34:00 +0000</pubDate>
      <description><![CDATA[<p>Tmux is an awesome tool. One of its greatest uses is that is allows
us to pair if you share the session. Anyway the setup is always a little
bit complicated if you want to do it really right. Let’s see what we can
do.</p>
<h1 id="the-simplest-scenario">The simplest scenario</h1>
<p>Let’s start in our computer all alone. Tmux sessions are run by a
daemon the same way as screen does.</p>
<p>Let’s create a new session where we will pair</p>
<pre><code>tmux new-session -s pair_programming
Now if you open a new terminal window you can attach your console to the same session.
tmux attach-session -t pair_programming</code></pre>
<p>Then you are sharing the session in the same computer. Easy, isn’t
it?</p>
<figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="http://joy.pm/content/media/2024/04/01_thumb.jpg" data-kg-custom-thumbnail>
<div class="kg-video-container">
<div class="kg-video-overlay">
<p><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMjMuMTQgMTAuNjA4IDIuMjUzLjE2NEExLjU1OSAxLjU1OSAwIDAgMCAwIDEuNTU3djIwLjg4N2ExLjU1OCAxLjU1OCAwIDAgMCAyLjI1MyAxLjM5MkwyMy4xNCAxMy4zOTNhMS41NTcgMS41NTcgMCAwIDAgMC0yLjc4NVoiIC8+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvc3ZnPg==" /></p>
</div>
<div class="kg-video-player-container kg-video-hide">
<div class="kg-video-player">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTIzLjE0IDEwLjYwOCAyLjI1My4xNjRBMS41NTkgMS41NTkgMCAwIDAgMCAxLjU1N3YyMC44ODdhMS41NTggMS41NTggMCAwIDAgMi4yNTMgMS4zOTJMMjMuMTQgMTMuMzkzYTEuNTU3IDEuNTU3IDAgMCAwIDAtMi43ODVaIiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zdmc+" />
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHJlY3QgeD0iMyIgeT0iMSIgd2lkdGg9IjciIGhlaWdodD0iMjIiIHJ4PSIxLjUiIHJ5PSIxLjUiIC8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHJlY3QgeD0iMTQiIHk9IjEiIHdpZHRoPSI3IiBoZWlnaHQ9IjIyIiByeD0iMS41IiByeT0iMS41IiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zdmc+" />
<span class="kg-video-current-time">0:00</span>
<div class="kg-video-time">
<p>/<span class="kg-video-duration">0:10</span></p>
</div>
<p>1×
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE1LjE4OSAyLjAyMWE5LjcyOCA5LjcyOCAwIDAgMC03LjkyNCA0Ljg1LjI0OS4yNDkgMCAwIDEtLjIyMS4xMzNINS4yNWEzIDMgMCAwIDAtMyAzdjJhMyAzIDAgMCAwIDMgM2gxLjc5NGEuMjQ5LjI0OSAwIDAgMSAuMjIxLjEzMyA5LjczIDkuNzMgMCAwIDAgNy45MjQgNC44NWguMDZhMSAxIDAgMCAwIDEtMVYzLjAyYTEgMSAwIDAgMC0xLjA2LS45OThaIiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zdmc+" />
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyNCAyNCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2LjE3NyA0LjNhLjI0OC4yNDggMCAwIDAgLjA3My0uMTc2di0xLjFhMSAxIDAgMCAwLTEuMDYxLTEgOS43MjggOS43MjggMCAwIDAtNy45MjQgNC44NS4yNDkuMjQ5IDAgMCAxLS4yMjEuMTMzSDUuMjVhMyAzIDAgMCAwLTMgM3YyYTMgMyAwIDAgMCAzIDNoLjExNGEuMjUxLjI1MSAwIDAgMCAuMTc3LS4wNzNaTTIzLjcwNyAxLjcwNkExIDEgMCAwIDAgMjIuMjkzLjI5MmwtMjIgMjJhMSAxIDAgMCAwIDAgMS40MTRsLjAwOS4wMDlhMSAxIDAgMCAwIDEuNDA1LS4wMDlsNi42My02LjYzMUEuMjUxLjI1MSAwIDAgMSA4LjUxNSAxN2EuMjQ1LjI0NSAwIDAgMSAuMTc3LjA3NSAxMC4wODEgMTAuMDgxIDAgMCAwIDYuNSAyLjkyIDEgMSAwIDAgMCAxLjA2MS0xVjkuMjY2YS4yNDcuMjQ3IDAgMCAxIC4wNzMtLjE3NloiIC8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3N2Zz4=" /></p>
</div>
</div>
</div>
</figure>
<p>Even this may be impressive it’s not very useful.</p>
<h1 id="in-the-same-network">In the same network</h1>
<p>Now let’s try something a bit better.</p>
<p>We just saw that we can share a tmux session between two terminal
sessions. Then we have a simple way of sharing the session over the
network. We can simply make other user to ssh into your computer and run
tmux from there.</p>
<p>Wait… Am I saying that it is a good idea to share your account with
your pair? No, don’t get me wrong. We can do a couple of things to
prevent this:</p>
<p>First, it is a good idea to create a local account only for your
pairs. For example i’ve created an account called pair in my computer.
Also it is a good idea to create a tmux group as we will see later. Add
both your user and pair user to that group.</p>
<p>Also instead of sharing the password for that account I’m using
certificates to handle who has access. That way it is super easy to
revoke the access once it is no longer necessary. There is one awesome
gem called github-auth that simplifies a lot the process. I can make
something like</p>
<pre><code>sudo su - pair
gh-auth add --users=&lt;my pair’s Github user&gt;</code></pre>
<p>And it will allow my pair to connect to my laptop without having to
deal with passwords or stuff like that. Also the pair user has access to
tmux so he can connect to my session and finally he can’t access to
anything else in my computer.</p>
<p>Also I can revoke access as simply as doing</p>
<pre><code>sudo su — pair
gh-auth remove --users=&lt;my pair’s Github user&gt;</code></pre>
<p>How does this work? Well, this works because all our Github public
keys are public. You can access yours through a url like:</p>
<pre><code>https://github.com/&lt;your username&gt;.keys</code></pre>
<p>Then this is using ssh’s authorized keys to allow a user to login as
pair user without having to type his password.</p>
<p>Let’s test this. I’m login with pair user and let’s see if I can
connect to one of my tmux sessions:</p>
<pre><code>tmux ls
failed to connect to server: No such file or directory</code></pre>
<p>What? Isn’t this enough? Well… no. Unfortunately we need to add an
extra small step. When we start a tmux session we need to provide a
socket so it is shared between different users. Let’s start our pairing
session with that new parameter:</p>
<pre><code>tmux -S /tmp/tmux_pair_socket new-session -s pair_session</code></pre>
<p>And we have created a new session and also exposing a socket so the
other user can connect. The bad thing with this approach is that the
socket will have wrong permissions for sure. That’s when the tmux group
comes handy. I’ve created a /var/tmux/ folder and gave it 770 permission
and changed the group to tmux. This way we can hold the socket there and
change the property of the socket to tmux group.</p>
<pre><code>mkdir /var/tmux
sudo chmod 770 /var/tmux
sudo chgrp tmux /var/tmux</code></pre>
<p>Then let’s create the socket there.</p>
<pre><code>tmux -S /var/tmux/pair_socket new-session -s pair_session</code></pre>
<p>And we will be able to see it from pair user.</p>
<pre><code>$ tmux -S /var/tmux/pair_socket ls
pair_session: 1 windows (created Sat Jul 11 11:40:08 2015) [78x30] (attached)</code></pre>
<p>And attach to it with</p>
<pre><code>tmux -S /var/tmux/pair_socket attach -t pair_session</code></pre>
<p>So. After all this config I only need to ask my partner to do:</p>
<pre><code>ssh pair@&lt;my IP&gt;
tmux -S /var/tmux/pair_socket attach -t pair_session</code></pre>
<p>And we will be sharing the terminal session with a super fast
latency.</p>
<h1 id="easing-things-a-little-bit">Easing things a little bit</h1>
<p>Anyway still the user I’m pairing with needs to know the location of
the socket. Let’s make the things a little bit easy for him.</p>
<p>We will make a small addition to /etc/sshd_config file so the pair
does not need to remember anything.</p>
<pre><code>Match User pair
  ForceCommand /usr/local/bin/tmux -S /var/tmux/pair_socket attach -t pair_session</code></pre>
<p>Then if some pair tries to login into my computer and there is no
pairing session prepared it will only output an error and if there is it
will directly connect to it. Piece of cake!</p>
<p>Also to start a new pairing session I’ve added an alias to my
.zshrc</p>
<pre><code>alias pair_tmux=’tmux -S /var/tmux/pair_socket new-session -s pair_session’
With all this I only have to type in a terminal:
pair_tmux</code></pre>
<p>and my pair just has to</p>
<pre><code>ssh pair@&lt;my ip&gt;</code></pre>
<p>And if the credentials are correct we just need to start coding!</p>
<p>Also I have the following alias to add permissions</p>
<pre><code>alias pair_adduser=&#39;sudo -u pair -i gh-auth add — users &#39;
alias pair_removeuser=&#39;sudo -u pair -i gh-auth remove — users &#39;</code></pre>
<p>This way I don’t need to switch user to give permission to somebody
to the pairing environment.</p>
<h1 id="firewalls">Firewalls</h1>
<p>Let’s say that you want to host a session and you are behind a
firewall. If you don’t want to change your firewall rules and you have a
public host you can as a last resort try to do a reverse tunnel.</p>
<p>If you don’t have your own host you can try using ngrok. It creates a
reverse tunnel for you so your pair can connect to it. We can just
do</p>
<pre><code>ngrok tcp 22</code></pre>
<p>And we will have a tunnel open that will be available to your pair
through the URL that appears in the screen. For example in the following
scenario</p>
<figure class="kg-card kg-image-card">
<img src="http://localhost:1313/posts/2015-07-11-pairing-over-tmux/02.png" class="kg-image" loading="lazy" width="1408" height="348" />
</figure>
<p>Our pair will have to connect using the following command</p>
<pre><code>ssh pair@0.tcp.ngrok.io -p 54147</code></pre>
<p>And that is it!</p>
<p>Just as a piece of advice avoid the homebrew version and download
from their website. Ngrok client is no longer open sourced and homebrew
will not be up to date.</p>
<p>Ngrok will also be free as long as only one client connects to your
TCP socket. If you need more that one person their pricing is really
reasonable.</p>
<h1 id="summary">Summary</h1>
<p>It takes a little bit of work to set up a nice setup to pair using
tmux but it is worth the effort. In the works case scenario I only need
to</p>
<pre><code>pair_tmux
ngrok tcp 22</code></pre>
<p>And share a URL for may pair to connect. Piece of cake. With this and
a hangout I can pair with only a tiny little bit of lag and both users
have control of the editor.</p>
]]></description>
    </item>
  </channel>
</rss>
