<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://bfgeek.com/">
  <title>Ian Kilpatrick's (bfgeek) Blog</title>
  <subtitle></subtitle>
  <link href="rss.xml" rel="self" />
  <link href="https://bfgeek.com/" />
  <updated>
    2026-07-19T22:29:40Z
  </updated>
  <id>https://bfgeek.com/</id>
  <author>
    <name>Ian Kilpatrick</name>
  </author>
  <entry>
    <title>What is Product Quality?</title>
    <link href="/product-quality/" />
    <updated>2026-07-19T22:29:40Z</updated>
    <id>/product-quality/</id>
    <content xml:lang="" type="html">
      <p>In my experience product quality is an extremely poorly understood concept in organisations.</p>
<figure>
  <img src="hero.jpg" width="1408" height="768">
  <figcaption>A Gemini generated image of the scene from <a href="https://www.youtube.com/watch?v=N9wsjroVlu8">Office Space</a> smashing the printer.</figcaption>
</figure>
<p>Typically there isn’t a shared understanding of what it means. Different people
have different mental models. As a result two people may both believe they are
improving product quality, but believe the other people are working on
something futile.</p>
<p>Let’s see what <a href="https://en.wikipedia.org/wiki/Quality_(business)">Wikipedia has to say</a>:</p>
<blockquote>
<p>In business, engineering, and manufacturing, quality has a pragmatic
interpretation as the conformity of something (goods or services); it is
defined as being suitable for the intended purpose (fitness for purpose) while
satisfying customer expectations.</p>
</blockquote>
<p>Great - thanks for that word soup.</p>
<p>As an exercise for you the reader - ask yourself how you define quality in
your product. Ask your colleagues. Better yet, if you really want a fun
exercise, ask your leadership - see if they come up with a similar word soup.</p>
<p>Your answers will likely vary wildly. Instead many organisations will agree on
some vanity metrics. These might include:</p>
<ul>
<li>Number of bugs open</li>
<li>Latency at the 99%-ile</li>
<li>User churn rate</li>
<li>CSAT (Customer Satisfaction Score)</li>
</ul>
<p>These metrics aren't bad in themselves, but they are proxies for quality.</p>
<p>You might be thinking to yourself - “Okay smarty-pants, what is quality then?”
I'd argue it is very simple, but difficult to measure.</p>
<blockquote>
<p><strong>Product quality is how the customer feels about your product - what emotions
does your product produce when used?</strong></p>
</blockquote>
<p>That's it. That's the whole ball game. If you squint other definitions match
this, but not before they've confused the hell out of everyone.</p>
<p>Now we get to the hard bit. What emotions do you want your customer to feel
about your product?</p>
<p>Broadly speaking emotions can be grouped into “positive” (<em>delight</em>, <em>pride</em>,
<em>interest</em>) and “negative” (<em>annoyance</em>, <em>irritation</em>, <em>fear</em>, <em>worry</em>).</p>
<p>The first thing you probably want to optimize for is to ensure your product
doesn’t trigger <em><strong>any</strong></em> major negative emotions. A single major negative
emotion can completely crowd out a dozen positive ones.</p>
<p>For example - if you produce a phone which <a href="https://en.wikipedia.org/wiki/Samsung_Galaxy_Note_7#Battery_explosions_and_recalls">spontaneously
combusts</a>
- even if the customer really loved the design/color - they’d be hard pressed
to ever trust your brand again.</p>
<p>A great recent example is <a href="https://www.youtube.com/watch?v=1WiQAOmESH0">Engineering Explained’s review of the Lucid
Air</a>. I implore you to watch this
video, and see the range of negative emotions. He bought this car and genuinely
believed this was the “best engineered vehicle the world has ever seen”. Jason
likely was excited/elated/etc by his new purchase. Only for a wave of negative
emotions to push those out.</p>
<p>What does this mean for you?</p>
<p>You should deeply care when customers are <em>frustrated</em>/<em>irritated</em>/<em>annoyed</em> by your
product. If you are trying to improve product quality, this is the first thing
you should focus on. Have a product that doesn’t reliably turn on the lights
when asked? You should probably fix that before anything else.  Have a product
that takes 10 seconds to respond to a query? You should probably fix that.</p>
<p>From this you can start building proxy metrics. E.g. latency at the 99%-ile
might matter! (Especially if it's 10 seconds!) But it might not matter
anymore once it's below 200ms. (What matters performance wise is a whole
separate blogpost which I should write someday).</p>
<p>The <em><strong>rate</strong></em> of incoming bug reports is also typically a good metric. “How
fast are people filing bugs compared to the last version?” (Assuming the way
people file bugs between versions is exactly the same).</p>
<p>An important thing to note here is that if a customer feels <em><strong>no emotion</strong></em>
that is a <em><strong>good thing</strong></em>. If a customer successfully pays some bill and feels
absolutely nothing, that’s great! It means that the experience was
frictionless and you made it as easy as possible.</p>
<p>Minor annoyances are also okay. If an animation occasionally stutters or a
setting is buried a couple of levels deep, that’s usually fine—as long as the
majority of your customers aren’t experiencing it. You shouldn’t go on a
crusade to ensure there are absolutely no negative emotions, you need to spend
your time wisely, focus on the major/severe issues (even if limited to a few
customers) or widespread ones.</p>
<p>Once a product has a solid foundation (your customer doesn’t want to throw your
product out a window), you can start to care about the minor annoyances if
needed, and the positive emotions.</p>
<p>A design refresh can really matter! It can take something boring to use and
make it delightful (think about how fun tapping a like button was the first
time popping up little hearts).
But this shouldn’t be at the cost of making the product harder to use and
frustrating customers.</p>
<p>Marketing really matters too! It can predispose the customer into feeling
excitement or anticipation when first using your product. This is also why the
OOBE (Out of Box Experience) is so important. Your first interaction with the
product predisposes the customer.</p>
<p>However a common trap for organisations is to believe that the positive
emotions e.g. triggering delight is the only thing that matters, and believe
that if customers “love” a product enough they will overlook the desire to
throw it out the window.</p>
<p>This isn’t the case.</p>
<p>So what should you do? Firstly make sure that everyone has a shared
understanding of what you are optimizing for. As I’ve argued for here I think a
customer's emotions define product quality. But you should get everyone singing
from the same hymn sheet.</p>
<p>Next ensure that your proxy metrics (latency, churn, bug rate) actually match
this shared understanding. One metric I hate for example is open bugs, the
metric you should actually care about is incoming bug rate (even if those
issues are merged into others).</p>
<p>Finally, when it comes to prioritization, use this quality framework to
advocate for what your customer actually needs - rather than a vanity project
that will be deprecated in six months.</p>
<p>Questions/thoughts/comments? Reach out to me on
<a href="https://bsky.app/profile/bfgeek.bsky.social">BlueSky</a>.</p>

    </content>
  </entry>
  <entry>
    <title>Things I'm proud of in 2025</title>
    <link href="/2025-review/" />
    <updated>2026-01-05T04:18:14Z</updated>
    <id>/2025-review/</id>
    <content xml:lang="" type="html">
      <p>This started off as a general &quot;2025 in-review&quot; but ended up too boring and too
long, so here are just a handful of things I'm proud of last year. These aren't
necessarily the most important work I did, or the most &quot;impactful&quot;, but things
that gave me joy, and hopefully will pay dividends in the future.</p>
<img src="hero.jpg" width="2509" height="1671">
<h3>flex-wrap: balance</h3>
<p>I spent a month or so implementing <code>flex-wrap: balance</code>. A lot of this time was
just spending time trying to understand different academic papers which
implement different solutions to the
<a href="https://en.wikipedia.org/wiki/Knuth%E2%80%93Plass_line-breaking_algorithm">Knuth-Plass line-breaking problem</a>.</p>
<p>In the end, some of the solutions were too difficult to understand (research
papers often gloss over important details, have poorly named variables, <a href="https://github.com/golang/go/blob/f8ee0f84753b22254d217bf28ce8ecca7db7025c/src/go/doc/comment/text.go#L307-L309">and
also have bugs</a>),
so I ended up <a href="https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/flex/flex_line_breaker.cc;l=21-55;drc=ed470f480a83a5a9e1b10eb186b225ab7bc376c1">implementing something</a>
different/new instead. I primarily did this so that it will be maintainable, at
some point someone might find an issue with the algorithm I used and will need
to fix it.</p>
<p>I settled on something that was <code>O(N)</code> in the common case, and I think converges
to <code>O(Nlog(N))</code> in the worst case (the naive solution is <code>O(N^2)</code> <a href="https://bsky.app/profile/randomascii.bsky.social/post/3lk4c6st7nc2e">which is truly
the worst complexity</a>).</p>
<p>Many people have wanted this feature since ~forever - hopefully we can give
this to developers soon.</p>
<p>I use <code>flex-wrap: balance</code> in my other <a href="https://bfgeek.com/flexbox-image-gallery/">blog
post</a> this year to build an image
gallery with flex.</p>
<img src="balance.jpg" width="1706" height="1350">
<h3>Flexbox Precision Issues</h3>
<p>Rounding errors typically don't matter that much for most applications. They do
in layout engines, however, as it often results in a &quot;gap&quot; between the content of
an element, and the element's border. This (rightfully) infuriates people.</p>
<p>Web layout engines use <a href="https://en.wikipedia.org/wiki/Fixed-point_arithmetic">fixed-point
arithmetic</a>, (instead of
floating-point) so when you divide a layout number by an integer you end up
with a remainder.</p>
<p>Fixed-point is used as floating-point gets more inaccurate the further away from
zero you get leading to gaps and other layout issues (someday I'll write a
&quot;floating-point is the
<a href="https://www.simonandschuster.com/series/The-Worst!-Series">worst</a>&quot; blog post).</p>
<p>Previously we discarded this remainder, and as such resulted in gaps when you
used <code>justify-content: space-between</code> on a flexbox as one example.</p>
<p>I implemented a <a href="https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/geometry/layout_unit_diffuser.h;drc=f7eff366e22a141571facc94d9352064689b5143">&quot;diffuser&quot;</a>
which can diffuse/distribute the remainder over N buckets in a &quot;nice&quot; way.
Under the hood this uses the core component of <a href="https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm">Bresenham's line drawing
algorithm</a> which is
a fun 2D graphics cross-over.</p>
<p>The other precision issue I worked on was rounding issues within the
<a href="https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/flex/line_flexer.cc;l=115-137;drc=137e3e7bc23789602d3a2e3db2ffa3766ccec71f">floating-point calculation</a>
of how flex-grow/flex-shrink are used. The solution I ended up with is actually
used within Gecko's <a href="https://searchfox.org/firefox-main/rev/21d2b7dcadb4f3d8789b84364a70ce103896047c/layout/generic/nsFlexContainerFrame.cpp#3247-3249">flex implementation</a>;
in hindsight, I should have read that first.</p>
<p>It works by calculating the fraction of the remaining available space (instead
of the total available space), which converges without underflow or overflow.</p>
<p>Fixing both these precision issues resulted in a non-trival number of bugs fixed.</p>
<h3>The SVG Viewport Dependence Optimization</h3>
<p>This was my first substantial optimization change for the year, as well as a
nice win code complexity wise. In the end it was only worth about 0.1% on
Speedometer3 but <a href="https://chromium-review.googlesource.com/c/chromium/src/+/6173723/8/third_party/blink/renderer/core/svg/svg_element.cc">removed a bunch of complex
logic</a>
maintaining HashMap on a hot codepath.</p>
<p><a href="https://chromium-review.googlesource.com/c/chromium/src/+/6168835">This optimization</a>
essentially removed a HashMap containing elements which had geometry which
depended on the SVG viewport (<code>&lt;rect width=&quot;50%&quot;&gt;</code>) as one example. It replaced
it with a couple of bits computed during layout instead.</p>
<p>Many performance/code-complexity improvements I've made within Blink have been
just removing HashMaps. It’s an interesting phenomenon, lots of developers will
reach for HashMaps as they are &quot;fast&quot; O(1) insertion/removal is hard to beat,
but often the slowness is caused by a piece of code looping over every element
within the map (making it just a fancy list at that point), or a HashMap placed
on a hotpath where the insert and contain operations eat CPU cycles.</p>
<p>The logic for maintaining these HashMaps are often very complex and fragile,
and in my experience leads to bugs in browser engines.</p>
<p>Maybe one day I'll write a &quot;HashMaps are the
<a href="https://www.simonandschuster.com/series/The-Worst!-Series">worst</a>&quot; blog post
detailing this further.</p>
<h3>Fixing <code>&lt;video&gt;</code> with <code>aspect-ratio: 4/3 auto</code></h3>
<p>There has been a <a href="https://github.com/w3c/csswg-drafts/issues/7524">relatively bad
bug</a> in Blink with the
<code>&lt;video&gt;</code> element and the aspect-ratio property, namely we ignored the
aspect-ratio coming from a video once it had loaded.</p>
<p>The issue with fixing this bug was that every engine did something different,
and since time immemorial the <code>&lt;video&gt;</code> element has had a default
<code>aspect-ratio</code> of <code>2:1</code>.</p>
<p>There were many web-platform-tests <a href="https://github.com/web-platform-tests/wpt/pull/54365/files">asserting this
behaviour</a>, as
everyone actually did the same thing for the basic case.</p>
<p>The final fix was <a href="https://chromium-review.googlesource.com/c/chromium/src/+/6641808/5/third_party/blink/renderer/core/layout/layout_video.cc">very
straight-forward</a>,
but first required <a href="https://github.com/w3c/csswg-drafts/issues/12053">getting everyone to
agree</a> that everyone was
wrong, and us proving that the proposed change was web-compatible (fortunately
<a href="https://www.hyrumslaw.com/">Hyrum's Law</a> didn't apply today).</p>
<h3>Other engines implementing &quot;block-in-inline&quot;</h3>
<p>A few years ago we removed a significant code-complexity burden from Blink
which is known as &quot;inline-splitting&quot;. We <a href="https://webkit.org/blog/115/webcore-rendering-ii-blocks-and-inlines/#:~:text=Blocks%20inside%20Inline%20Flows">inherited
this</a>
from WebKit and caused major issues for us (and a security issue or two). The
CSS2 spec also (unhelpfully) <a href="https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level:~:text=are%20broken%20around%20the%20block%2Dlevel%20box">suggests that
engines</a>
should implement this problem this way.</p>
<p>Why it's bad is a whole &quot;Ian rants at you for an hour&quot; type scenario - so let's
not get into that today.</p>
<p>We've since ripped this code out, and now both
<a href="https://bugs.webkit.org/show_bug.cgi?id=303236">WebKit</a> and
<a href="https://github.com/servo/servo/pull/41492">Servo</a> appear to be implementing
this approach as well last year (hurray!) and this should fix some well known
bugs.</p>
<p>I don’t think I can claim the original idea, (it potentially was a suggestion from
an EdgeHTML engineer), but I believe it is evidence that working in the open,
and writing down how/why you did something can help out others in the same
space later on.</p>
<h3>Merging Anonymous Table Parts</h3>
<p>My final piece of work in 2025 was trying to fix a cluster of issues caused by
not merging anonymous table-parts correctly.</p>
<p>When you have an element with <code>display: table-cell</code> it implicitly creates a
<code>table</code>, <code>table-section</code>, <code>table-row</code> surrounding it. If you have two <code>display: table-cell</code>s next to each other they belong to the same anonymous table, etc.</p>
<p>We had a long standing issue that when you removed or changed the <code>display</code> of
an element, we didn't &quot;merge&quot; these contiguous anonymous table objects correctly.</p>
<p>There have been attempts to fix this within Blink before, but ended up causing
crashes and other issues. I spent a bunch of pre-work fixing things that might
have caused crashes previously, and <a href="https://chromium-review.googlesource.com/c/chromium/src/+/7244131">landed a
patch</a> to
merge these objects. This should be out in the wild in February (so fingers
crossed), and should fix a non-trivial amount of bugs for web-developers.</p>
<hr>
<p>There are lots of other things I did work-wise that didn't make the cut
(helping ship width:stretch, CSS anchor positioning fixes, various performance
optimizations, removing <code>-webkit-box</code> quirks).</p>
<p>Let's see where 2026 takes us!</p>

    </content>
  </entry>
  <entry>
    <title>Exploiting flex-grow for an Image Gallery</title>
    <link href="/flexbox-image-gallery/" />
    <updated>2025-09-13T17:49:35Z</updated>
    <id>/flexbox-image-gallery/</id>
    <content xml:lang="" type="html">
      <img src="hero.jpg" width="1706" height="1350">
<p>There are lots of different ways to construct image galleries on the web. When
building these it's desirable to:</p>
<ul>
<li>Maintain the aspect-ratio of the images.</li>
<li>Reduce/remove any &quot;whitespace&quot;/&quot;free-space&quot; present (ideally you want all
the images to fill the entire space available) aka <em>full-bleed</em>.</li>
</ul>
<p>These two constraints are often at odds with each other.
<a href="https://medium.com/google-design/google-photos-45b714dfbed1">Google photos has previously described in detail</a>
how they have previously tried to solve this. A simple solution which
web-developers often use is a wrapping flexbox, however this often leaves
whitespace at the end of each flex-line (leaving a ragged edge).</p>
<p>There is a solution to this by using the <code>flex-grow</code> property (potentially one of
the few reasons to use flex-grow with a non-zero or one value).</p>
<pre><code class="language-css">.gallery {
  display: flex;
  flex-wrap: wrap;
  flex-wrap: balance; /* "balance" will be better once available. */
  gap: 24px;
}

/*
  This assumes the content is of the form &lt;img width=100 height=100>
  However, it can be adjusted on any content via. the aspect-ratio property.
*/
img {
  --ar: attr(width type(&lt;number>)) / attr(height type(&lt;number>));
  width: calc(20% * var(--ar));
  height: auto;
  flex-grow: calc(var(--ar));
}</code></pre>
<p>That's it!</p>
<h3>What is this magic?!?</h3>
<p>How this works is a little quirky:</p>
<ol>
<li>
<p>The images in their initial form (with <code>width: 20%</code>) will look something like this:</p>
<img src="initial.jpg" width="1638" height="1479">
<p>First we calculate the aspect-ratio from the <code>width</code> and <code>height</code> attributes on
the image, via. the new <code>attr()</code> function available in CSS:</p>
<pre><code class="language-css">img {
  --ar: attr(width type(&lt;number>)) / attr(height type(&lt;number>));
}</code></pre>
<blockquote>
<p><code>attr()</code> is currently <a href="https://developer.chrome.com/blog/advanced-attr">only available</a>
in Chromium based browsers, but could be set manually via script.</p>
</blockquote>
</li>
<li>
<p>We use this aspect-ratio to override the <code>width</code> of the images. By setting the
<code>width</code> proportional to the apsect-ratio (<code>calc(20% * var(--ar))</code>), we
ensure that every image will have the <strong>same height</strong> before wrapping and
growing. For example:</p>
<img src="partial.jpg" width="1638" height="1083">
<p>At this stage we are left with images which all have the same height, but
have a ragged edge once broken into lines.</p>
</li>
<li>
<p>We then use <code>flex-grow</code> so that each image will <strong>grow proportionally</strong> to
its aspect-ratio. This ensures that each image will keep the same height as
others on the line, but will fill the remaining space.</p>
<img src="hero.jpg" width="1706" height="1350">
<p>Each line will end up with a slightly different size, but all the images
keep their aspect-ratio!</p>
</li>
</ol>
<h3>Downsides?</h3>
<p>Like anything there are some potential downsides.</p>
<ul>
<li>
<p>We are ignoring the pixel sizes of the images, it's possible under this
scheme to scale up an image past its natural pixel size (causing pixelation).</p>
</li>
<li>
<p>Due to the way flex line-breaking works, it's possible for a single image to
be placed on a single line. This often looks bad, but will be mostly fixed
once <a href="https://github.com/w3c/csswg-drafts/issues/3070">flexbox receives</a>
<code>flex-wrap: balance</code>.</p>
<blockquote>
<p>Chromium/Blink has an implementation of <code>flex-wrap: balance</code> behind
<code>chrome://flags/#enable-experimental-web-platform-features</code>.</p>
</blockquote>
</li>
</ul>
<p>Both of these issues can be mitigated by adding something like:</p>
<pre><code class="language-css">img {
  max-width: min(100%, calc(50% * var(--ar)));
}</code></pre>
<p>This will prevent images with too much space on a line from growing too much.
But will potentially leave some space on the line.</p>
<h3>Questions?</h3>
<p>If you have any suggestions/comments/questions or a cool name for this
technique feel free reach to me on <a href="https://bsky.app/profile/bfgeek.bsky.social">Bluesky</a>.</p>
<p>A JSFiddle of this approach can be <a href="https://jsfiddle.net/3d2hewqt/">found here</a>.</p>
<p>Images used in the demo(s) are from <a href="https://picsum.photos">Lorem Picsum</a>.</p>
<p>A live demo is below:</p>
<iframe src="demo.html" style="width: 100%; aspect-ratio: 4/3"></iframe>

    </content>
  </entry>
</feed>

