<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Björn Tillenius</title>
        <description></description>      
        <link>https://tillenius.me</link>
        <atom:link href="https://tillenius.me/feed.xml" rel="self" type="application/rss+xml" />
        
            <item>
                <title>An LLM wrote 100% of my code</title>
                <description>&lt;p&gt;As many of you, I often see the claim that an LLM, or rather an LLM agent
framework (like Claude Code or GitHub Copilot), wrote 100% of someone’s code.&lt;/p&gt;

&lt;p&gt;I’ve always been curious whether this could be true or not. I started playing around
with using LLMs in my daily work as a software engineer at the end of last summer. In
the beginning it was truly a struggle. I used an LLM to write code, but I also had to
spend a lot of time reviewing and refactoring the code it generated. At that time, it
also made simple mistakes, like redefining variables or functions, generating code that
wasn’t correct, and so on.&lt;/p&gt;

&lt;p&gt;However, even though I didn’t feel that I was more productive using an LLM than writing
the code myself, I continued trying to make use of LLMs in my work, trying different models,
different frameworks, learning how to prompt, and so on. I learned a lot, but I’ve also
seen a great improvement in the agent frameworks that are out there. For example, they can
now connect to an LSP server and run unit tests automatically, so they can verify and fix
their code, before they give it to me to review.&lt;/p&gt;

&lt;p&gt;I’m now at a point where for a couple of greenfield projects, I’ve used an LLM and agent framework
to write 100% of the code. I finally feel that I’m productive with an LLM agent
than without.&lt;/p&gt;

&lt;p&gt;I now also know that the claim that an LLM wrote 100% of the code can in fact be true.&lt;/p&gt;

&lt;h2 id=&quot;100-writing-code--100-engineering&quot;&gt;100% writing code != 100% engineering&lt;/h2&gt;

&lt;p&gt;It should be pointed out that usually the claim is that the LLM &lt;strong&gt;wrote&lt;/strong&gt; 100% of the code.
Writing code is only one part of the engineering process. When using LLMs for software
engineering, it’s important to understand what an LLM is great at doing, and what it’s
not great at doing.&lt;/p&gt;

&lt;p&gt;For example, an LLM is great at generating code quickly. It can write code much faster
than any human out there. However, it’s not great at keeping a large codebase maintainable.
At least not without guidance from a human software engineer.&lt;/p&gt;

&lt;p&gt;An LLM is great when the instructions are clear, and there are already well-known solutions
to the problems. They are great at inferring complicated patterns, but they often fail to
understand subtle nuances. A great example of that is the “Car wash” problem, which proves
to be challenging to understand for an LLM. It basically goes like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;You: I need to wash my car. The car wash is 300 meters away. Should I drive or walk there?
LLM: Walk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The LLM focuses on the most common problem, driving or walking, but fails to understand the
nuance that the car needs to get to the car wash somehow. Even frontier models like Claude
Opus 4.6 get this wrong. I tested it today, and it got it slightly better, but still failed:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;That&apos;s a fun little dilemma! For 300 meters, I&apos;d say walk — that&apos;s only about a 3–4 minute stroll. Driving such a short distance barely warms up the engine and uses fuel for no real benefit.

That said, if you need to drive the car through an automatic car wash (the kind where you stay in the car), then obviously you&apos;d need to drive it there. But if it&apos;s a self-service or hand wash place, walking over is the way to go — maybe even enjoy the fresh air on the way.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Is this someone that you would trust to make important decisions about your architecture
and codebase?&lt;/p&gt;

&lt;h2 id=&quot;agent-feedback-loop&quot;&gt;Agent feedback loop&lt;/h2&gt;

&lt;p&gt;I’m not going to go into details here exactly what I’m doing. I might write another blog
post about that in the future. The details are important, but I also don’t think there
is a single best way of doing things. I think people need to try out different approaches
and see what works best for them. I did enjoy reading what
&lt;a href=&quot;https://mitchellh.com/writing/my-ai-adoption-journey&quot;&gt;Mitchell Hashimoto wrote&lt;/a&gt;, and I feel
that my story is quite similar.&lt;/p&gt;

&lt;p&gt;However, something that I do think is important is that you have a feedback loop with your
LLM agents. It basically goes like this:&lt;/p&gt;

&lt;svg version=&quot;1.1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 488 433&quot; width=&quot;100%&quot;&gt;&lt;!-- svg-source:excalidraw --&gt;&lt;metadata&gt;&lt;/metadata&gt;&lt;defs&gt;&lt;style class=&quot;style-fonts&quot;&gt;
      @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }&lt;/style&gt;
&lt;style&gt;
  :root { --svg-bg: #f7fafb; --svg-fg: #3a4145; }
  @media (prefers-color-scheme: dark) {
    :root { --svg-bg: #1e2a3a; --svg-fg: #f7fafb; }
  }
  path[fill=&quot;#1e2a3a&quot;] { fill: var(--svg-bg); }
  path[stroke=&quot;#f7fafb&quot;] { stroke: var(--svg-fg); }
  text[fill=&quot;#f7fafb&quot;] { fill: var(--svg-fg); }
&lt;/style&gt;&lt;/defs&gt;&lt;g stroke-linecap=&quot;round&quot; transform=&quot;translate(10 10) rotate(0 234 206.5)&quot;&gt;&lt;path d=&quot;M-1.54 0.2 L469.42 1.45 L469.83 412.53 L1.86 411.94&quot; stroke=&quot;none&quot; stroke-width=&quot;0&quot; fill=&quot;#1e2a3a&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M0 0 C117.06 0.99, 234.06 0.42, 468 0 M0 0 C141.57 2.13, 283.19 1.98, 468 0 M468 0 C467.95 116.2, 467.98 231.29, 468 413 M468 0 C466.04 98.49, 466.26 197.03, 468 413 M468 413 C362.57 412.41, 258.53 414.05, 0 413 M468 413 C315.06 412.81, 161.53 413.01, 0 413 M0 413 C-2.6 273.45, -2.43 132.9, 0 0 M0 413 C0.41 327.41, 0.92 242.29, 0 0&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g stroke-linecap=&quot;round&quot; transform=&quot;translate(27 38) rotate(0 84 72.5)&quot;&gt;&lt;path d=&quot;M32 0 C72.72 1.43, 114.84 -1.2, 136 0 M32 0 C69.85 -0.12, 108.14 0.02, 136 0 M136 0 C155.95 0.8, 167.87 10.7, 168 32 M136 0 C158.62 -1.3, 166.7 9.69, 168 32 M168 32 C166.81 56.86, 166 81.15, 168 113 M168 32 C169.35 58.17, 168 85.93, 168 113 M168 113 C167.01 132.54, 157.91 143.77, 136 145 M168 113 C169.56 132.37, 156.97 145.63, 136 145 M136 145 C99.16 143.14, 61.42 143.67, 32 145 M136 145 C107.15 143.99, 77.43 144.46, 32 145 M32 145 C12.12 143.87, -1.13 134.79, 0 113 M32 145 C11.33 146.26, -1.07 133.38, 0 113 M0 113 C-0.73 84.13, 0.87 55.78, 0 32 M0 113 C-0.15 83.87, -1.3 53.35, 0 32 M0 32 C-1.05 11.65, 10.12 -0.87, 32 0 M0 32 C0.71 9.16, 10.01 -0.7, 32 0&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(74.54999923706055 88) rotate(0 36.45000076293945 22.5)&quot;&gt;&lt;text x=&quot;36.45000076293945&quot; y=&quot;31.716&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;36px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;Plan&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap=&quot;round&quot; transform=&quot;translate(286 41.5) rotate(0 84 72.5)&quot;&gt;&lt;path d=&quot;M32 0 C55.77 -1.27, 82.71 0.04, 136 0 M32 0 C71.51 1.67, 109.06 0.57, 136 0 M136 0 C156.13 0.18, 169.93 11.07, 168 32 M136 0 C156.06 -0.77, 170.1 9.33, 168 32 M168 32 C168.16 58.44, 168.12 81.95, 168 113 M168 32 C168.1 56.47, 167.5 80.58, 168 113 M168 113 C166.59 133.41, 158.18 145.34, 136 145 M168 113 C169.85 133.67, 157.26 145.07, 136 145 M136 145 C108.34 143.85, 82.85 145.28, 32 145 M136 145 C98.31 144.39, 60.66 145.23, 32 145 M32 145 C9.99 143.13, 0.81 134.3, 0 113 M32 145 C11.86 145.89, 0.32 135.31, 0 113 M0 113 C-0.69 89.97, -0.23 62.24, 0 32 M0 113 C-0.45 95.19, 1.29 79.04, 0 32 M0 32 C-0.9 10.58, 10.86 -1.88, 32 0 M0 32 C-0.86 8.44, 8.44 1.45, 32 0&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(292.5999984741211 91.5) rotate(0 77.4000015258789 22.5)&quot;&gt;&lt;text x=&quot;77.4000015258789&quot; y=&quot;31.716&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;36px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;Delegate&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap=&quot;round&quot; transform=&quot;translate(283 258.5) rotate(0 84 72.5)&quot;&gt;&lt;path d=&quot;M32 0 C63.04 0.16, 96.52 -0.39, 136 0 M32 0 C67.07 0.99, 104.95 -0.45, 136 0 M136 0 C158.46 -1.79, 168.03 9.41, 168 32 M136 0 C156.19 1.34, 168.54 10.71, 168 32 M168 32 C170.16 62.95, 170.32 90.73, 168 113 M168 32 C167.4 61.52, 167.6 89.97, 168 113 M168 113 C169.71 134.88, 158.88 143.12, 136 145 M168 113 C168.01 133.85, 156.41 145.58, 136 145 M136 145 C96.35 143.11, 57.65 144.61, 32 145 M136 145 C96.52 145.76, 58.94 144.97, 32 145 M32 145 C12.35 144.54, 1.55 135.87, 0 113 M32 145 C9.01 145.37, -0.9 133, 0 113 M0 113 C-1.62 96.57, 1.05 76.19, 0 32 M0 113 C0.13 95.13, 0 76.02, 0 32 M0 32 C-0.63 9.56, 12.6 -1.92, 32 0 M0 32 C1.98 12.2, 11 -0.86, 32 0&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(308.0999984741211 308.5) rotate(0 58.900001525878906 22.5)&quot;&gt;&lt;text x=&quot;58.900001525878906&quot; y=&quot;31.716&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;36px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;Review&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap=&quot;round&quot; transform=&quot;translate(31 259.5) rotate(0 84 72.5)&quot;&gt;&lt;path d=&quot;M32 0 C71.24 0.71, 113.51 0.19, 136 0 M32 0 C68.64 -0.39, 103.51 -0.71, 136 0 M136 0 C157.97 1.24, 169.88 10.88, 168 32 M136 0 C155.58 -1.42, 168.09 11.65, 168 32 M168 32 C168.44 54.48, 169.97 76.6, 168 113 M168 32 C169.05 53.08, 168.81 75.19, 168 113 M168 113 C167.72 132.97, 156.63 143.42, 136 145 M168 113 C169.74 136.52, 157.3 147.09, 136 145 M136 145 C96.53 146.6, 60.24 147.17, 32 145 M136 145 C99.49 143.79, 61.31 144.47, 32 145 M32 145 C10.31 143.13, -1.45 134.14, 0 113 M32 145 C8.53 146.7, 0.7 133.16, 0 113 M0 113 C0.9 83.52, 0.76 54.3, 0 32 M0 113 C0.75 91.76, 0.58 70.18, 0 32 M0 32 C-0.96 9.65, 10.8 -1.22, 32 0 M0 32 C0.24 8.92, 11.59 1.54, 32 0&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(59.608333587646484 309.5) rotate(0 55.391666412353516 22.5)&quot;&gt;&lt;text x=&quot;55.391666412353516&quot; y=&quot;31.716&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;36px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;Refine&lt;/text&gt;&lt;/g&gt;&lt;g mask=&quot;url(#mask-iqfAh0wb8IN6Efkj5aOfT)&quot; stroke-linecap=&quot;round&quot;&gt;&lt;g transform=&quot;translate(194 111) rotate(0 46.71741220019754 -0.362706736874884)&quot;&gt;&lt;path d=&quot;M-0.55 0.04 C15.05 -0.18, 78.11 -0.77, 93.98 -0.83 M1.37 -0.99 C16.77 -1.12, 77.54 -0.03, 93.03 0.27&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(194 111) rotate(0 46.71741220019754 -0.362706736874884)&quot;&gt;&lt;path d=&quot;M69.38 8.4 C77.31 5.76, 83.77 4.19, 93.03 0.27 M69.38 8.4 C75.7 6.43, 82.14 4.66, 93.03 0.27&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(194 111) rotate(0 46.71741220019754 -0.362706736874884)&quot;&gt;&lt;path d=&quot;M69.69 -8.7 C77.59 -5.63, 83.94 -1.49, 93.03 0.27 M69.69 -8.7 C75.89 -5.99, 82.24 -3.09, 93.03 0.27&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;mask id=&quot;mask-iqfAh0wb8IN6Efkj5aOfT&quot;&gt;&lt;rect x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot; width=&quot;388&quot; height=&quot;211&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;236.73333311080933&quot; y=&quot;98.5&quot; fill=&quot;#000&quot; width=&quot;8.533333778381348&quot; height=&quot;25&quot; opacity=&quot;1&quot;&gt;&lt;/rect&gt;&lt;/mask&gt;&lt;g transform=&quot;translate(236.73333311080933 98.5) rotate(0 3.984079089388217 12.137293263125116)&quot;&gt;&lt;text x=&quot;4.266666889190674&quot; y=&quot;17.619999999999997&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;20px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;1&lt;/text&gt;&lt;/g&gt;&lt;g mask=&quot;url(#mask-nDbt5Mzt_K_fh7AlVCXGr)&quot; stroke-linecap=&quot;round&quot;&gt;&lt;g transform=&quot;translate(369 185) rotate(0 -0.05774342738573068 37.16653922902418)&quot;&gt;&lt;path d=&quot;M1.02 -0.1 C0.97 12.27, 0.22 61.66, -0.1 74.34 M0.1 -1.19 C-0.14 11.31, -1.34 62.97, -1.11 75.52&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(369 185) rotate(0 -0.05774342738573068 37.16653922902418)&quot;&gt;&lt;path d=&quot;M-9.52 51.98 C-6.99 59.2, -5.27 65.31, -1.11 75.52 M-9.52 51.98 C-6.06 60.23, -3.92 70.52, -1.11 75.52&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(369 185) rotate(0 -0.05774342738573068 37.16653922902418)&quot;&gt;&lt;path d=&quot;M7.58 52.08 C4.69 59.41, 0.99 65.49, -1.11 75.52 M7.58 52.08 C4.53 60.27, 0.15 70.52, -1.11 75.52&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;mask id=&quot;mask-nDbt5Mzt_K_fh7AlVCXGr&quot;&gt;&lt;rect x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot; width=&quot;469&quot; height=&quot;360&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;362&quot; y=&quot;210&quot; fill=&quot;#000&quot; width=&quot;14&quot; height=&quot;25&quot; opacity=&quot;1&quot;&gt;&lt;/rect&gt;&lt;/mask&gt;&lt;g transform=&quot;translate(362 210) rotate(0 6.942256572614269 12.166539229024181)&quot;&gt;&lt;text x=&quot;7&quot; y=&quot;17.619999999999997&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;20px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;2&lt;/text&gt;&lt;/g&gt;&lt;g mask=&quot;url(#mask-BrPZb6BNk0TSq8nPhz26N)&quot; stroke-linecap=&quot;round&quot;&gt;&lt;g transform=&quot;translate(284 328) rotate(0 -43.59007172067652 -1.3350849244743586)&quot;&gt;&lt;path d=&quot;M-0.04 -0.12 C-14.41 -0.49, -71.86 -2.08, -86.47 -2.55 M-1.52 -1.22 C-15.99 -1.41, -73.2 -1.16, -87.14 -1.31&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(284 328) rotate(0 -43.59007172067652 -1.3350849244743586)&quot;&gt;&lt;path d=&quot;M-63.63 -9.8 C-70.43 -6.54, -77.76 -6.21, -87.14 -1.31 M-63.63 -9.8 C-68.62 -7.42, -74.87 -5.57, -87.14 -1.31&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(284 328) rotate(0 -43.59007172067652 -1.3350849244743586)&quot;&gt;&lt;path d=&quot;M-63.67 7.3 C-70.42 5.76, -77.73 1.29, -87.14 -1.31 M-63.67 7.3 C-68.7 5.92, -74.94 3.99, -87.14 -1.31&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;mask id=&quot;mask-BrPZb6BNk0TSq8nPhz26N&quot;&gt;&lt;rect x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot; width=&quot;471&quot; height=&quot;430&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;234.41666650772095&quot; y=&quot;314.5&quot; fill=&quot;#000&quot; width=&quot;12.166666984558105&quot; height=&quot;25&quot; opacity=&quot;1&quot;&gt;&lt;/rect&gt;&lt;/mask&gt;&lt;g transform=&quot;translate(234.41666650772095 314.5) rotate(0 5.993261771602533 12.164915075525641)&quot;&gt;&lt;text x=&quot;6.083333492279053&quot; y=&quot;17.619999999999997&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;20px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;3&lt;/text&gt;&lt;/g&gt;&lt;g mask=&quot;url(#mask-uXRcqOadSbeJVagcrlcP_)&quot; stroke-linecap=&quot;round&quot;&gt;&lt;g transform=&quot;translate(113.93705098039209 260.5) rotate(0 -0.83835192297704 -39.908218695428246)&quot;&gt;&lt;path d=&quot;M-0.06 -0.26 C-0.22 -13.15, -0.71 -64.62, -0.95 -77.8 M-1.55 -1.44 C-1.78 -14.64, -1.3 -66.72, -1.39 -79.55&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(113.93705098039209 260.5) rotate(0 -0.83835192297704 -39.908218695428246)&quot;&gt;&lt;path d=&quot;M7.12 -56.04 C4.22 -61.45, 3.28 -69.44, -1.39 -79.55 M7.12 -56.04 C5.3 -61.52, 2.48 -66.74, -1.39 -79.55&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform=&quot;translate(113.93705098039209 260.5) rotate(0 -0.83835192297704 -39.908218695428246)&quot;&gt;&lt;path d=&quot;M-9.99 -56.08 C-7.76 -61.47, -3.58 -69.44, -1.39 -79.55 M-9.99 -56.08 C-7.88 -61.44, -6.78 -66.66, -1.39 -79.55&quot; stroke=&quot;#f7fafb&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;mask id=&quot;mask-uXRcqOadSbeJVagcrlcP_&quot;&gt;&lt;rect x=&quot;0&quot; y=&quot;0&quot; fill=&quot;#fff&quot; width=&quot;215.87410196078417&quot; height=&quot;439&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;107.11852558556348&quot; y=&quot;208.75&quot; fill=&quot;#000&quot; width=&quot;11.699999809265137&quot; height=&quot;25&quot; opacity=&quot;1&quot;&gt;&lt;/rect&gt;&lt;/mask&gt;&lt;g transform=&quot;translate(107.11852558556348 208.75) rotate(0 5.980173471851572 11.841781304571754)&quot;&gt;&lt;text x=&quot;5.849999904632568&quot; y=&quot;17.619999999999997&quot; font-family=&quot;Excalifont, Xiaolai, sans-serif, Segoe UI Emoji&quot; font-size=&quot;20px&quot; fill=&quot;#f7fafb&quot; text-anchor=&quot;middle&quot; style=&quot;white-space: pre;&quot; direction=&quot;ltr&quot; dominant-baseline=&quot;alphabetic&quot;&gt;4&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;

&lt;p&gt;Now, let’s go through each step in more detail.&lt;/p&gt;

&lt;h2 id=&quot;step-1-plan&quot;&gt;Step 1: Plan&lt;/h2&gt;

&lt;p&gt;This is where you, as a software engineer, make the architectural decisions and create
a plan for the LLM agent to follow. The better plan you make, the better the LLM agent
will perform.&lt;/p&gt;

&lt;p&gt;This step doesn’t have to be completely manual. You can make use of an LLM agent to help
you create the plan. The important thing is that the plan is something that you take
accountability for. You are the one that is creating the plan, even if you get help from
an LLM agent.&lt;/p&gt;

&lt;h2 id=&quot;step-2-delegate&quot;&gt;Step 2: Delegate&lt;/h2&gt;

&lt;p&gt;When you have a plan, you can give it to an LLM agent to execute. Again, I’m not going to
go into details here, because there are many ways of doing this. For example, most likely
you will divide up the plan into smaller tasks, and give the LLM agent one task at a time.&lt;/p&gt;

&lt;p&gt;For each task, you can also have a feedback loop, so that you can give course corrections
early if you see that things don’t go the way you expected.&lt;/p&gt;

&lt;h2 id=&quot;step-3-review&quot;&gt;Step 3: Review&lt;/h2&gt;

&lt;p&gt;This is probably the most important step. It’s now that you verify that the LLM agent
wrote the code in the way that you expected. Remember that you are the software engineer.
You understand what the code needs to look like in order to keep the codebase maintainable
and scalable.&lt;/p&gt;

&lt;p&gt;This is where I currently spend the most amount of time. I go through the code carefully,
and make sure that it matches what I would have written myself. Sometimes it’s the LLM agent
that did something unexpected. Sometimes it’s the plan that is lacking in details. But
sometimes it could be that I only now realize that the code should be structured differently.&lt;/p&gt;

&lt;p&gt;It’s also not about code structure and architecture. It includes running the code to make
sure it works, do a security review of the code, checking for subtle bugs, and so on.&lt;/p&gt;

&lt;p&gt;Remember, you can still use an LLM agent at this step as well. For me, it helps me produce
better quality code, since doing large refactorings before I submit a PR is much easier than
it used to be.&lt;/p&gt;

&lt;p&gt;The goal of the feedback loop is to reduce the amount of time you spend here.&lt;/p&gt;

&lt;h2 id=&quot;step-4-refine&quot;&gt;Step 4: Refine&lt;/h2&gt;

&lt;p&gt;This is how you reduce the amount of time you spend in Step 3. You need to look at what
you needed to do in Step 3 and try to improve things so that the LLM agent can do a better
job in the future.&lt;/p&gt;

&lt;p&gt;Again, I’m not going into details here, since there are many ways of doing this, and it
might depend on whether you use Claude Code, Codex, GitHub Copilot, or something else.&lt;/p&gt;

&lt;p&gt;But you should be aware of how your LLM agent gets the context it needs in order to perform
its task. I will give some examples here.&lt;/p&gt;

&lt;h3 id=&quot;prompt&quot;&gt;Prompt&lt;/h3&gt;

&lt;p&gt;The prompt is the most obvious one, since it’s usually how you tell the LLM agent what to do.
You should think about how you write the prompt, but since this is something that you need
to do for every feedback loop, it’s one of the least scalable solutions.&lt;/p&gt;

&lt;h3 id=&quot;planmd&quot;&gt;PLAN.md&lt;/h3&gt;

&lt;p&gt;For larger tasks, it’s often helpful to create a document that contains the plan, with the
general outline what needs to be done. But it can also contain example code, breakdowns of
the task into subtasks, links to relevant documentation, and so on.&lt;/p&gt;

&lt;p&gt;Take some time to reflect on whether your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PLAN.md&lt;/code&gt; could have been better.&lt;/p&gt;

&lt;h3 id=&quot;agentsmd&quot;&gt;AGENTS.md&lt;/h3&gt;

&lt;p&gt;A lot of LLM agents automatically read &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; if it exists for your project. This is
a great place to put project-specific instructions. For example, if you see that the
LLM agent added a lot of useless comments all over the place, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; is a great
place to add a note that comments should be added only to explain non-obvious code.&lt;/p&gt;

&lt;p&gt;As long as the rules make sense on a project level, this is a good place to put them.&lt;/p&gt;

&lt;h3 id=&quot;skills&quot;&gt;Skills&lt;/h3&gt;

&lt;p&gt;Modifying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; is not always the answer, since you need approval from other
team members. For rules that don’t have a team-wide consensus, you can create personal
prompt files. Something that automatically gets included in every prompt you write.&lt;/p&gt;

&lt;p&gt;There are again many ways of doing this, &lt;a href=&quot;https://agentskills.io/home&quot;&gt;agent skills&lt;/a&gt; is one
example. Some LLM agents support it, some don’t.&lt;/p&gt;

&lt;p&gt;The idea is that you can create a library that guides the LLM agent for specific tasks.&lt;/p&gt;

&lt;p&gt;In the case of agent skills, they can either live locally
on your filesystem only, or be included in the project repository, as a complement to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;documentation&quot;&gt;Documentation&lt;/h3&gt;

&lt;p&gt;Another way of guiding the LLM agent is to make sure that your code and architecture
are well-documented. LLM agents usually can read documentation when they need to get
more context, and you can also reference the specific documentation in your plan.&lt;/p&gt;

&lt;p&gt;This is something that helps both LLM agents and humans to understand your codebase.&lt;/p&gt;

&lt;h2 id=&quot;vibecoding&quot;&gt;Vibecoding&lt;/h2&gt;

&lt;p&gt;When talking about an LLM writing 100% of the code, it’s hard to not think about
vibecoding. I see a lot of people talking about it, and I also feel that some
people don’t really understand what it means.&lt;/p&gt;

&lt;p&gt;What I talk about in this post is not vibecoding. Vibecoding is a term
&lt;a href=&quot;https://x.com/karpathy/status/1886192184808149383&quot;&gt;coined by Andrej Karpathy&lt;/a&gt;.
Basically, they define it as using LLMs to write code, where you trust the LLM
to produce the correct code. You give the responsibility of maintaining the codebase
to an LLM agent.&lt;/p&gt;

&lt;p&gt;If you’ve read this far, it should be clear that what I’ve been talking about in this
post is not vibecoding. I’m not yet convinced that an LLM agent can maintain a large
codebase in the long term, at least not currently. The LLM agents and models do improve
rapidly, but I still feel that they have a long way to go in this area.&lt;/p&gt;

&lt;h3 id=&quot;vibe-coding-vs-agentic-coding&quot;&gt;Vibe coding vs agentic coding&lt;/h3&gt;

&lt;p&gt;Agentic coding is basically when you move away from using the LLM models through
a basic chat interface. Instead you use something like Claude Code that has agents
that can look at your codebase, talk to LSP language servers, run unit tests, and
even interact with other agents.&lt;/p&gt;

&lt;p&gt;Agent engineering has improved the quality of the state of code, but it’s not
really related to vibecoding. Even if you use agent engineering, you can still
choose whether you do vibecoding, where you trust the LLM agents fully. Or you
can choose to not do vibecoding, and instead take ownership of the codebase yourself.&lt;/p&gt;

&lt;h3 id=&quot;is-vibecoding-always-bad&quot;&gt;Is vibecoding always bad?&lt;/h3&gt;

&lt;p&gt;Of course not! Let’s say that you’re writing a one-off script, doing a functional
prototype to show off some workflow, or small projects that you don’t anticipate
will be maintained over time.&lt;/p&gt;

&lt;p&gt;In that case, vibecoding can be a great accelerator, since you will save a lot
of time compared to writing it manually, or carefully reviewing the code.&lt;/p&gt;
</description>
                <pubDate>Sun, 15 Mar 2026 09:00:00 +0000</pubDate>
                <link>https://tillenius.me/blog/2026/03/15/an-llm-wrote-100-percent-of-my-code</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2026/03/15/an-llm-wrote-100-percent-of-my-code</guid>
            </item>
        
            <item>
                <title>Using smbus with Python 3 on a Raspberry Pi</title>
                <description>&lt;p&gt;A while ago I got an Orange Matchbox, which is a
&lt;a href=&quot;https://www.raspberrypi.org/&quot;&gt;Raspberry Pi&lt;/a&gt; running
&lt;a href=&quot;https://developer.ubuntu.com/en/snappy/&quot;&gt;Snappy Ubuntu Core&lt;/a&gt; together
with an Ubuntu branded case and a
&lt;a href=&quot;https://shop.pimoroni.com/products/piglow&quot;&gt;PiGlow&lt;/a&gt;. The 
&lt;a href=&quot;http://people.canonical.com/~platform/snappy/raspberrypi2/&quot;&gt;Snappy image for Raspberry Pi&lt;/a&gt;
recently got support for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;I2C&lt;/code&gt;, so I tried to drive
the PiGlow using Python 3. It turned out it wasn’t trivial, since
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-smbus&lt;/code&gt; doesn’t support Python 3 out of the box. Further more, you
can’t install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.debs&lt;/code&gt; in Snappy, so installing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-smbus&lt;/code&gt; for Python 2
doesn’t work either. And it’s a C extension, so you can’t just copy
over the sources.&lt;/p&gt;

&lt;p&gt;I’ve seen some posts about how you can patch the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-smbus&lt;/code&gt; source
code and recompile it for Python 3. That’s a lot of work when you just
want to try and play around a bit. I instead took a stab at
reimplementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-smbus&lt;/code&gt; in pure Python. You can find the result
here:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://github.com/bjornt/pysmbus
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It only implements two methods so far, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_byte_data&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_byte_data&lt;/code&gt;, but that goes a long way. I also patched the
&lt;a href=&quot;https://github.com/benleb/PyGlow&quot;&gt;PyGlow&lt;/a&gt; package to support Python3
and not to require &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RPi.GPIO&lt;/code&gt;, which also is a
C extension I don’t want to recompile:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://github.com/bjornt/PyGlow/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With these two packages, we can now drive the PiGlow using Python 3 in
Snappy Ubuntu Core. First we assemble the code on our desktop machine
and copy it to the Raspberry Pi:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;desktop:~&amp;gt; git clone https://github.com/bjornt/PyGlow/
desktop:~&amp;gt; cd PyGlow
desktop:~/PyGlow&amp;gt; wget https://github.com/bjornt/pysmbus/raw/master/smbus.py
desktop:~/PyGlow&amp;gt; scp -r ../PyGlow ubuntu@pi2:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, on the Raspberry Pi, we run the bin_block.py example. Note that we
have to use sudo, so that we get permission to access the I2C bus:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;desktop:~/PyGlow&amp;gt; ssh ubuntu@pi2
(RaspberryPi2)ubuntu@localhost:~$ cd PyGlow
(RaspberryPi2)ubuntu@localhost:~/PyGlow$
(RaspberryPi2)ubuntu@localhost:~/PyGlow$ sudo PYTHONPATH=. python3 examples/bin_clock.py
 Hour | Minute | Second
01111 | 011001 | 111010 (15:25:58)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we look at our Raspberry Pi, we should see the PiGlow’s lights working:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/orange-matchbox-lights.jpg&quot; alt=&quot;PiGlow glowing&quot; /&gt;&lt;/p&gt;
</description>
                <pubDate>Sun, 06 Sep 2015 15:50:00 +0000</pubDate>
                <link>https://tillenius.me/blog/2015/09/06/raspberry-pi-smbus-python-3</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2015/09/06/raspberry-pi-smbus-python-3</guid>
            </item>
        
            <item>
                <title>VLAN tagging with Linksys E8350</title>
                <description>&lt;p&gt;I bought a &lt;a href=&quot;http://www.linksys.com/us/p/P-E8350/&quot;&gt;Linksys E8350&lt;/a&gt; router to
replace the router my ISP gave me as well as an old Linksys WRT610N router.  I
chose the E8350, since from the setup screenshots it looked like it could do
VLAN tagging, which I need. I was disappointed at first, since the UI doesn’t
allow you to do VLAN tagging for the LAN ports. But it turned out that with a
little bit of hacking, it’s possible to do it.&lt;/p&gt;

&lt;p&gt;Doing VLAN tagging with 5 ports is quite limiting, but just about enough
for my use case. I get a, somewhat odd, trunk from my ISP which has a VLAN
for IPTV traffic, but regular Internet packets aren’t tagged. I have the
router in my bedroom and want to access both the LAN and IPTV networks in
my living room. But I have only a single cable connecting the rooms, so I
use VLANs to be able to connect both networks with one cable.&lt;/p&gt;

&lt;p&gt;The first attempt to make port 4 the trunk for the IPTV and LAN VLANs
failed:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vlan-fail.png&quot; alt=&quot;Failed VLAN taggging attempt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s odd, because the UI looks like it would allow creating a trunk on
the LAN ports. A closer look at the Javascript that does the validation
revealed this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    for(i=1;i&amp;lt;VLAN_ENTRY;i++)
    {
        obj = getEle(ele_id_name[ele]+i);
        if(obj.value != port_option[2])
        {
            if(flag == true){
                //[share.js]errmsg.err104=&quot;A VLAN port can be untagged to only one VLAN ID.&quot;;
                alert(errmsg.err104);
                obj.focus();
                return false;
            }
            flag = true;
        }
    }
    return true;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The commented out error message makes sense. A port can’t be untagged in
different VLANs. But the actual code checks also that a port can’t be
tagged in different VLANs, and that doesn’t make much sense. It could be a
bug, but considering that the actual error message that was displayed
doesn’t mention ‘untagged’, it could be that they took an existing VLAN UI
from some other product, and just crippled it. The E8350 is a consumer
model after all.&lt;/p&gt;

&lt;p&gt;Now, what would happen if the validation above would be disabled? The
Javascript is in the HTML file, and Chromium Developer Tool doesn’t
allow such Javascript to be edited. Instead, let’s put a breakpoint
whenever &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flag&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; and use the Javascript console to set it
back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/vlan-success.png&quot; alt=&quot;Successful VLAN taggging attempt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Look at that, it worked!&lt;/p&gt;
</description>
                <pubDate>Sun, 31 May 2015 14:00:00 +0000</pubDate>
                <link>https://tillenius.me/blog/2015/05/31/linksys-e8350-vlan-tagging</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2015/05/31/linksys-e8350-vlan-tagging</guid>
            </item>
        
            <item>
                <title>Enabling middleclick scrolling in Ubuntu for Lenovo clickpads</title>
                <description>&lt;p&gt;The short version is that if you want to enable middleclick scrolling
for Lenovo clickpads in Ubuntu, do this in a terminal:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo add-apt-repository ppa:bjornt/evdev
sudo apt-get update
sudo apt-get dist-upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The commands above should upgrade the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xserver-xorg-input-evdev&lt;/code&gt; package,
as well as remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xserver-xorg-input-synaptics&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xserver-xorg-input-all&lt;/code&gt; packages.&lt;/p&gt;

&lt;p&gt;Next you need to create a file at
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/share/X11/xorg.conf.d/90-clickpad.conf&lt;/code&gt; with the following contents:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Section &quot;InputClass&quot;
    Identifier &quot;Clickpad&quot;
    MatchIsTouchpad &quot;on&quot;
    MatchDevicePath &quot;/dev/input/event*&quot;
    Driver &quot;evdev&quot;
    # Synaptics options come here.
    Option &quot;Clickpad&quot; &quot;true&quot;
    option &quot;EmulatedMidButtonTime&quot; &quot;0&quot;
    Option &quot;SoftButtonAreas&quot; &quot;65% 0 0 40% 42% 65% 0 40%&quot;
    Option &quot;AreaBottomEdge&quot; &quot;0%&quot;
EndSection

Section &quot;InputClass&quot;
    Identifier   &quot;TrackPoint&quot;
    MatchProduct &quot;TrackPoint&quot;
    MatchDriver  &quot;evdev&quot;
    Option       &quot;EmulateWheel&quot;       &quot;1&quot;
    Option       &quot;EmulateWheelButton&quot; &quot;2&quot;
    Option       &quot;XAxisMapping&quot;       &quot;6 7&quot;
EndSection
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The interesting options are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SoftButtonAreas&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AreaBottomEdge&lt;/code&gt;.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SoftButtonAreas&lt;/code&gt; specifies where the buttons should be. If you want the
buttons at the top, itt should generally be in the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;R 0 0 H L R 0 H&quot;&lt;/code&gt;,
where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; is where the border between the middle and right buttons, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H&lt;/code&gt;
is the height of the buttons, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; is the border between the middle and
left buttons.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AreaBottomEdge&lt;/code&gt; turns off the touchpad, expect for clicking. If you
want to keep using the touchpad, you can instead specify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AreaTopEdge&lt;/code&gt;,
with the same value you use for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H&lt;/code&gt;. That would enable the touchpad below
the buttons.&lt;/p&gt;

&lt;p&gt;Unfortunately, you can’t specify where the left button should be,
instead if occupies everything that isn’t the middle or right button.
This is a bit annoying, since at least I tend to touch the touchpad with
my palm when reaching for the middle button, which will result in a left
click being registered instead of a middle click.&lt;/p&gt;

&lt;p&gt;I created this package because Ubuntu doesn’t quite support the
clickpads that come in the newer Lenovo laptops. Ubuntu does support
clickpads, and with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SoftButtonAreas&lt;/code&gt; config settings it’s possible to
have three soft buttons on the clickpad where the real buttons used to
be. However, what’s not supported out of the box is middleclick
scrolling, where you hold the middle button and scroll with the
trackpoint.&lt;/p&gt;

&lt;p&gt;The main problem is that the clickpad is driven by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;synaptics&lt;/code&gt; and
the trackpoint by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evdev&lt;/code&gt;, and they can’t communicate to generate the
scroll events. Bae Taegil &lt;a href=&quot;https://aur.archlinux.org/packages/xf86-input-evdev-trackpoint/&quot;&gt;patched the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evdev&lt;/code&gt; driver&lt;/a&gt; to basically include
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;synaptics&lt;/code&gt; driver. I’ve taken that patch and generated a package for
Ubuntu 14.04. I’ve only added a package for Trusty, but I could add packages
for other releases if needed. I will most likely add one for Utopic,
when it becomes more stable.&lt;/p&gt;
</description>
                <pubDate>Tue, 19 Aug 2014 08:30:00 +0000</pubDate>
                <link>https://tillenius.me/blog/2014/08/19/ubuntu-clickpad-middle-scroll</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2014/08/19/ubuntu-clickpad-middle-scroll</guid>
            </item>
        
            <item>
                <title>Using web.go on Google App Engine</title>
                <description>&lt;p&gt;In short, if you want to use &lt;a href=&quot;http://www.getwebgo.com/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.go&lt;/code&gt;&lt;/a&gt; in
&lt;a href=&quot;http://code.google.com/appengine/docs/go/overview.html&quot;&gt;Google App Engine’s Go runtime environment&lt;/a&gt;, check out
&lt;a href=&quot;https://github.com/bjornt/web.go/tree/google-app-engine&quot;&gt;my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;google-app-engine&lt;/code&gt; branch of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.go&lt;/code&gt;&lt;/a&gt;.
Using that one you can start using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.go&lt;/code&gt; like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package webgoexample

import (
    &quot;http&quot;
    &quot;log&quot;
    &quot;os&quot;
    &quot;web&quot;
)

var server *web.Server

func init() {
    server = &amp;amp;web.Server{
        Config: web.Config,
        Logger: log.New(os.Stdout, &quot;&quot;, log.Ldate|log.Ltime)}
    server.Get(&quot;/&quot;, func(ctx *web.Context) {
        ctx.Write([]uint8(&quot;Hello from web.go!&quot;))
    })

    // Send all requests to web.go.
    http.HandleFunc(&quot;/&quot;, handler)
}

func handler(writer http.ResponseWriter, request *http.Request) {
    server.ServeHTTP(writer, request)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;BTW, if you simply put the &lt;a href=&quot;http://www.getwebgo.com/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.go&lt;/code&gt;&lt;/a&gt; branch in your root dir,
you have to remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examples/&lt;/code&gt; directory, otherwise &lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;App Engine&lt;/a&gt; won’t be able to compile your
project.&lt;/p&gt;

&lt;p&gt;The branch in question has quite minimal changes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.go&lt;/code&gt;, but I haven’t
proposed to merge it to trunk yet, since it removes some functionality. First
of all I changed it not to set up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/debug/&lt;/code&gt; paths, so that I could remove the
use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http/pprof&lt;/code&gt;, since it’s not available on &lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;App Engine&lt;/a&gt;. After that I had
to also remove the use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net.ResolveTCPAddr&lt;/code&gt;, which is also not available on
App Engine. I basically replaced it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net.SplitHostPort&lt;/code&gt;, which I suspect
is good enough. It doesn’t resolve host names and port names, but I’d be
surprised if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hr.RemoteAddr&lt;/code&gt; wouldn’t be an IP address and a port number.&lt;/p&gt;
</description>
                <pubDate>Mon, 07 Nov 2011 13:50:46 +0000</pubDate>
                <link>https://tillenius.me/blog/2011/11/07/using-webgo-on-google-app-engine</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2011/11/07/using-webgo-on-google-app-engine</guid>
            </item>
        
            <item>
                <title>formataddr() and unicode</title>
                <description>&lt;p&gt;I often see code like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;message[&quot;To&quot;] = formataddr((name, email))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This looks like it should work, especially since the docstring of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;formataddr()&lt;/code&gt;
says that it will return a string value suitable for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;To&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cc&lt;/code&gt; header.
However, while it works most of the time, it fails if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; is a
unicode string containing non-ascii characters. It may look ok if you look
simply read &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message[&quot;To&quot;]&lt;/code&gt;, but as soon as you convert the message or header to
a byte string, you will see the problem.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from email.Message import Message
&amp;gt;&amp;gt;&amp;gt; from email.Utils import formataddr
&amp;gt;&amp;gt;&amp;gt; msg = Message()
&amp;gt;&amp;gt;&amp;gt; msg[&quot;To&quot;] = formataddr((u&quot;Björn&quot;, &quot;bjorn@tillenius.me&quot;))
&amp;gt;&amp;gt;&amp;gt; msg[&quot;To&quot;]
u&apos;Bj\xf6rn &amp;lt;bjorn@tillenius.me&amp;gt;&apos;
&amp;gt;&amp;gt;&amp;gt; msg.as_string()
&apos;To: =?utf-8?b?QmrDtnJuIDxiam9ybkB0aWxsZW5pdXMubWU+?=\n\n&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Most code that will use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;To&lt;/code&gt; address in the example will fail, since there’s no visible e-mail address in there. The header should look like this, i.e. only the name itself should be encoded:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;To: =?utf-8?b?QmrDtnJu?= &amp;lt;bjorn@tillenius.me&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I wish Python would handle this better. I usually end up writing a helper
function like this for projects I work on:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def format_address(name, email):
    email = str(email)
    if not name:
        return email
    name = str(Header(name))
    return formataddr((name, email))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
                <pubDate>Fri, 11 Feb 2011 14:44:07 +0000</pubDate>
                <link>https://tillenius.me/blog/2011/02/11/formataddr-and-unicode</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2011/02/11/formataddr-and-unicode</guid>
            </item>
        
            <item>
                <title>Problems with drive-by fixes</title>
                <description>&lt;p&gt;To start with, I think drive-by fixes are great. If you see that 
something is wrong when fixing something else, it can be a good idea to
fix it right away, since otherwise you probably won’t do it.&lt;/p&gt;

&lt;p&gt;However, even when doing drive-by fixes, I still think that &lt;strong&gt;each
landed branch should focus on one thing only.&lt;/strong&gt; As soon as you start to
group unrelated things together, you make more work for others. It might
be easier for you, but think about all the people that are going to look
at your changes. Please, don’t be lazy! It doesn’t take much work to
extract the drive-by fix into a separate branch, and most importantly to
land it separately. If you do find that it’s too time-consuming to do this, let’s talk, and see what is taking time. There should be something we can do to make it easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There’s no such thing as a risk-free drive-by fix.&lt;/strong&gt; There’s always the
potential of something going wrong (even if the application is 
well-tested). When something goes wrong, someone needs to go back and 
look at what was done. Now, if you land the drive-by fix together with 
unrelated (or even related) changes, you basically hide it. By reducing
your workload slightly, you create much more work for someone else.&lt;/p&gt;

&lt;p&gt;For example, on Friday I saw that we had some problems with scripts in 
&lt;a href=&quot;https://launchpad.net&quot;&gt;Launchpad&lt;/a&gt;. They were trying to write to a mail
directory, to which they didn’t have access. That was odd, since scripts have
always talked to the SMTP servers directly, and didn’t use the queued mailer
that needed write access to that directory. Looking through the recent commit
logs didn’t reveal anything. Luckily enough, William Grant pointed out that 
&lt;a href=&quot;http://bazaar.launchpad.net/~launchpad-pqm/launchpad/db-devel/revision/9205&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r9205&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db-devel&lt;/code&gt;&lt;/a&gt;
contained a change to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sendmail.py&lt;/code&gt;, which probably was the cause of the
problems. This turned out to be correct, but it was still pretty much 
impossible to see why that change was made. I decided that the best thing
to do was to revert the change, but I wasn’t sure exactly what to 
revert. That diff of that revision is more than 4000 lines, and more 
than 70 files were changed. So how can I know which other files were 
change to accommodate the change in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sendmail.py&lt;/code&gt;. I tried looking at 
the commit logs, but that didn’t reveal much. The only thing I could do 
was to revert the change in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sendmail.py&lt;/code&gt; and send it off to ec2, 
waiting three hours to see if anything broke.
So, I plead again, if you do drive-by fixes (and you should), &lt;strong&gt;please spend a few minutes extra, to extract the fix into a separate branch, and land it separately!&lt;/strong&gt; 
Is there maybe anything we can do to make this easier to do?&lt;/p&gt;
</description>
                <pubDate>Mon, 03 May 2010 12:10:47 +0000</pubDate>
                <link>https://tillenius.me/blog/2010/05/03/problems-with-drive-by-fixes</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2010/05/03/problems-with-drive-by-fixes</guid>
            </item>
        
            <item>
                <title>RFC: Expose features to users when they are done, not once a month</title>
                <description>&lt;p&gt;I’ve been working on &lt;a href=&quot;https://dev.launchpad.net/MergeWorkflowDraft&quot;&gt;a new release/merge workflow&lt;/a&gt; for Launchpad.
I’ve written it from the developers’ point of view, but I’d love some
comments from users of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt;, so let me try to explain how you, as
users, would be affected by this change.&lt;/p&gt;

&lt;p&gt;The proposal is that we would decouple our feature development cycles
from our release cycles. We would more or less get rid of our releases,
and push features to our users when they are ready to be used. Every
feature would first go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt;, and when it’s considered
good enough, it will get pushed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt; for everyone to use.
Bug fixes would also go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt; first, and pushed to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt; when they are confirmed to work. Sadly, Launchpad will still go down once a month for updating DB and other regular maintenance, just like before. The amount (and frequency) of downtime would stay the same as before.&lt;/p&gt;

&lt;p&gt;There are users who are in our beta team and use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt;
all the time, and those who want a more stable Launchpad, and use
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;users-of-launchpadnet&quot;&gt;Users of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Those who aren’t in the beta team would get bug fixes sooner than with
the current workflow. Instead of having to wait for the end of the
release cycle, they will get it as soon as the fix has been confirmed to
work on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt;. The same is true for features, kind of.
These users would have to wait a bit longer than today, since today we
push even unfinished features to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt; users at the end of the
release cycle. With the new workflow, these users would have to wait for
the feature to be considered complete, but in return these user should get
a better experience when seeing the feature for the first time.&lt;/p&gt;

&lt;p&gt;One potential source of problem is that even though fixes and features
get tested on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt;, before going to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt;,
with each update there is the potential of some other issue being introduced. For example, fixing a layout bug on one page, might make another page look different.
With the current workflow this happens only once a month, instead of a
few times every month with the new workflow. That said, even today we
update &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt; multiple times every month, to fix more serious
issues.&lt;/p&gt;

&lt;h2 id=&quot;users-of-edgelaunchpadnet&quot;&gt;Users of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;If you are in the beta team, and use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt; on a regular
basis, it won’t be that different from how it works today. Just like today, you
would be exposed to features that are under development. What would
change is that we will try to do a better job at telling you which
features that are on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt; only. This way you will have a
better chance at actually helping us test and use the features, and
tell us about any problems, so that we can fix it right away. This
should make you more aware of new features that are being added to Launchpad, and provide a better opportunity for you to make it better.&lt;/p&gt;

&lt;p&gt;One potential source of problem here is that developers will know that
their work won’t end up on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt;, before they say it’s ready,
so they push more rough features to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt;. Thus it could be
a more rockier ride than today. But of course, our developers care a lot 
about their users, so they won’t land their work, unless it’s really good! :-)&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;My hope is that this will provide a better and stable experience for users of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad.net&lt;/code&gt;, and provide users of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edge.launchpad.net&lt;/code&gt; a better opportunity to help us making new features rock! But I’m interested to hear what you, the actual users, think about this.&lt;/p&gt;
</description>
                <pubDate>Thu, 04 Mar 2010 18:56:41 +0000</pubDate>
                <link>https://tillenius.me/blog/2010/03/04/expose-features-when-they-are-done</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2010/03/04/expose-features-when-they-are-done</guid>
            </item>
        
            <item>
                <title>Technical Architect Roadmap</title>
                <description>&lt;p&gt;I made the transition from the Bugs team lead to the Launchpad Technical Architect quite a
while ago. While the first time was spent mainly on satisfying my
coding desires, it’s time to define what I’m going to spend my time as
technical architect! My road map that shows the high level things that
I’ll be focusing on is available here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://dev.launchpad.net/TechnicalArchitect&quot;&gt;https://dev.launchpad.net/TechnicalArchitect&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll also be writing blog posts (and sending mails to the launchpad-dev
mailing list of course) regularly to keep people updated with my
progress and findings. My blog is at &lt;a href=&quot;http://tillenius.me/&quot;&gt;http://tillenius.me/&lt;/a&gt; and I tag all
posts related to Launchpad with &lt;a href=&quot;http://tillenius.me/blog/tag/launchpad/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchpad&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m currently working on &lt;a href=&quot;https://dev.launchpad.net/LEP/ReleaseFeaturesWhenTheyAreDone&quot;&gt;decoupling our feature development cycles with
our release cycles&lt;/a&gt;, which I do mainly, because I think it’s important, not because it’s part of the technical architect’s responsibility.
But in parallel with that my next task is to set up a team that can help
me doing a good job. I’ll expand more about the team in another post,
but in short it will consist of members from each sub-team in Launchpad.
It will act as a forum to discuss what needs my attention, and they will
also help me figuring out solutions to problems, and help me implement
the solutions.&lt;/p&gt;

&lt;p&gt;One of the first major tasks will be to come up with a testing strategy.
Currently when we write tests we don’t think that much about it.
Everyone have their preferences, and we have a wide variety of testing
styles, making it hard to find which code paths are exercised by which
tests, and how good test coverage we have. This leads to us sometimes having
bad test coverage, and some times having too much test
coverage, i.e. we have redundant tests that make the test suite slower.
Coming up with guidelines on how to write tests, which kind of tests to
write, where to place them, etc., is the first step. But we also need to
figure out how to make our test suite faster, what kind of documentation to 
provide, and so on.&lt;/p&gt;

&lt;p&gt;In addition to the tasks on the roadmap, I also have a number of things I do on a regular basis. This includes reviewing database patches for API consistency, help teams design features from a technical point of view, keep my eyes open for areas in the code that need refactoring and clean-up.&lt;/p&gt;
</description>
                <pubDate>Wed, 24 Feb 2010 12:12:23 +0000</pubDate>
                <link>https://tillenius.me/blog/2010/02/24/technical-architect-roadmap</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2010/02/24/technical-architect-roadmap</guid>
            </item>
        
            <item>
                <title>Windmill - so far, so good</title>
                <description>&lt;p&gt;We’ve used Windmill in our Launchpad buildbots for a while now, and it’s actually
worked out quite well. I was afraid that we would have a lot of fallout,
since in the beginning Windmill was fragile and caused a lot of
intermittent test failure. However, so far I’d said that we’ve had very
little problems. There was one intermittent failure, but it was known
from the beginning that it would fail eventually. Apart from that we’ve
had only one major issue, and that’s that something is using 100% CPU
when our combined Javascript file is bigger than 512 000 bytes. This
stopped people from landing Javascript additions for a while, and we
still haven’t resolved this issue, apart from making the file smaller.&lt;/p&gt;

&lt;p&gt;There are some things that would be nice to improve with regards to
Windmill. The most important thing is to make sure that launchpad.js can
be bigger than 512 000 bytes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://launchpad.net/bugs/519744&quot;&gt;https://launchpad.net/bugs/519744&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It would also be nice to make the test output nicer. At the moment
Windmill logs quite a lot to stderr, making it look like the test
failed, even though it didn’t. We don’t want Windmill to log anything
really, unless it’s a critical failure:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://launchpad.net/bugs/494519&quot;&gt;https://launchpad.net/bugs/494519&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was going to say that we also have some problems related to logging in
(because we look at a possibly stale page to decide whether the user is
logged in), but it seems like Salgado already fixed it!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://launchpad.net/bugs/515494&quot;&gt;https://launchpad.net/bugs/515494&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It would also be nice to investigate whether the problem with asserting
a node directly after waiting for it sometimes fails. We had problems
like that before; code was waiting for an element, and when using
assertNode directly after the wait, the node still didn’t exist. I
haven’t seen any test fail like that lately, so it might have been fixed
somehow:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://launchpad.net/bugs/487666&quot;&gt;https://launchpad.net/bugs/487666&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are some other things I could think of that would be nice to have.
I haven’t found any bugs filed for them, but I’ll list them here.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Don’t run the whole test suite under xvfb-run. It’d be better to   start xvfb only for the Windmill tests.&lt;/li&gt;
  &lt;li&gt;Use xvfb by default for Windmill tests. When running the Windmill      tests it’s quite annoying to have Firefox pop up now and then. It’d      be better to run them headless by default.&lt;/li&gt;
  &lt;li&gt;Switches for making debugging easier. Currently we shut down      Firefox after running the Windmill tests. It should be possible to      have Firefox remain running after the test has finished running, so that you can manually poke around if you want to. If we use xvfb by default, we also need a switch for not using it.&lt;/li&gt;
&lt;/ul&gt;
</description>
                <pubDate>Wed, 10 Feb 2010 12:51:17 +0000</pubDate>
                <link>https://tillenius.me/blog/2010/02/10/windmill-so-far-so-good</link>
                <guid isPermaLink="true">https://tillenius.me/blog/2010/02/10/windmill-so-far-so-good</guid>
            </item>
        
    </channel>
</rss>
