<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/assets/styles/feed.xsl" media="screen" ?>


<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title type="text">Blog</title>
  <subtitle type="text">A blog with experiments in progress, scattered notes, and the occasional odd trail of thought.</subtitle>

  <id>https://gary.place/blog/feed.xml</id>
  <link rel="alternate" type="text/html" href="https://gary.place/"/>
  <link rel="self" type="application/atom+xml" href="https://gary.place/blog/feed.xml"/>

  <updated>2025-11-08T00:00:00Z</updated>
  <author>
    <name>KuluGary</name>
    <email>kululu.gary@gmail.com</email>
  </author>
  <generator uri="https://www.11ty.dev/">Eleventy.js</generator>
      <entry>
        <title>Learning p5.js week five: Worley Noise</title>
        <link href="https://gary.place/blog/learning-p5-js-week-five/"/>
        <updated>2025-11-08T00:00:00Z</updated>
        <id>https://gary.place/blog/learning-p5-js-week-five/</id>
        <summary type="html">
          Week five of p5.js sketches: This time I try to implement Worley Noise, a different approach to OpenSimplex Noise, the one I used for Marching Squares.
        </summary>
        <content type="html">
          &lt;p&gt;This time I&#39;ll be sketching the &lt;strong&gt;Worley Noise&lt;/strong&gt; algorithm following &lt;a href=&quot;https://www.youtube.com/watch?v=4066MndcyCk&quot;&gt;this implementation&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-worley-noise&quot; tabindex=&quot;-1&quot;&gt;What is Worley Noise &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-five/#what-is-worley-noise&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Worley_noise&quot;&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Gradient_noise&quot; title=&quot;Gradient noise&quot;&gt;noise&lt;/a&gt; function [...] that outputs a real value at a given coordinate that corresponds to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Distance&quot; title=&quot;Distance&quot;&gt;distance&lt;/a&gt; of the nth nearest seed (usually n=1) and the seeds are distributed evenly through the region.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Explained more simply, Worley noise is a way to make natural-looking patterns by measuring how far each point in space is from a set of random “feature points.”&lt;/p&gt;
&lt;p&gt;It&#39;s often used in computer graphics to create textures that look like stone, water, cells, or bubbles, because the distance-based patterns feel organic and irregular.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How It Works &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-five/#how-it-works&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The sketch builds a noise texture called Worley noise.&lt;/p&gt;
&lt;p&gt;It begins by scattering random &amp;quot;feature points&amp;quot; throughout a 3D space, then for each pixel in a 2D image, it calculates how far that pixel&#39;s (x, y, z) position is from the nearest feature points. These distances are used to determine texture intensity and shape.&lt;/p&gt;
&lt;p&gt;The two closest distances are blended through a contrast function and mapped to colors from a palette, producing smooth, cell-like patterns. Each animation frame uses a different z-slice of the 3D space, creating an evolving effect.&lt;/p&gt;
&lt;h2 id=&quot;final-result&quot; tabindex=&quot;-1&quot;&gt;Final result &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-five/#final-result&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;iframe src=&quot;https://editor.p5js.org/KuluGary/full/6WtRz8N7G&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-five/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4066MndcyCk&quot;&gt;Coding Worley Noise&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rhythmiccanvas.com/research/papers/worley.pdf&quot;&gt;A Cellular Texture Basis Function&lt;/a&gt; by &lt;strong&gt;Steven Worley&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

        </content>
      </entry>
      <entry>
        <title>Learning p5.js week four: Hilbert Curve</title>
        <link href="https://gary.place/blog/learning-p5-js-week-four/"/>
        <updated>2025-11-01T00:00:00Z</updated>
        <id>https://gary.place/blog/learning-p5-js-week-four/</id>
        <summary type="html">
          Week four of p5.js sketches: Implementing the Hilbert curve algorithm to draw the classic infinite space-filling curve.
        </summary>
        <content type="html">
          &lt;p&gt;In this week&#39;s experiment I&#39;ll be diving into the &lt;strong&gt;Hilbert curve&lt;/strong&gt; algorithm following along this video from &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-hilbert-curve&quot; tabindex=&quot;-1&quot;&gt;What is the Hilbert Curve &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-four/#what-is-the-hilbert-curve&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Hilbert_curve&quot;&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] a &lt;a href=&quot;https://en.wikipedia.org/wiki/Geometric_continuity&quot; title=&quot;Geometric continuity&quot;&gt;continuous&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Fractal_curve&quot; title=&quot;Fractal curve&quot;&gt;fractal&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Space-filling_curve&quot; title=&quot;Space-filling curve&quot;&gt;space-filling curve&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In simpler terms, it&#39;s a mathematical curve that can theoretically fill an entire two-dimensional area by following a precise recursive pattern.&lt;/p&gt;
&lt;p&gt;Each iteration (called an &lt;em&gt;order&lt;/em&gt;) increases the resolution, bending the line tighter and tighter until it nearly becomes solid.&lt;/p&gt;
&lt;p&gt;Hilbert curves show up in computer science, image compression, and even dithering algorithms for grayscale rendering.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How It Works &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-four/#how-it-works&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The sketch builds the curve point by point.&lt;/p&gt;
&lt;p&gt;Each position in the sequence is converted into an (x, y) coordinate using a &lt;strong&gt;Hilbert index function&lt;/strong&gt;: a mapping from a single integer to a two-dimensional location.&lt;/p&gt;
&lt;p&gt;For every frame:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The curve progresses one segment forward.&lt;/li&gt;
&lt;li&gt;Each segment&#39;s color is smoothly interpolated along a palette, creating a gradient through the path.&lt;/li&gt;
&lt;li&gt;Once the final point is reached, the animation halts, revealing the entire pattern.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The algorithm scales with order so higher orders produce increasingly dense, woven paths that almost resemble clothing fibers under a microscope.&lt;/p&gt;
&lt;h2 id=&quot;final-result&quot; tabindex=&quot;-1&quot;&gt;Final result &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-four/#final-result&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;iframe src=&quot;https://editor.p5js.org/KuluGary/full/qTREixd8E&quot; height=&quot;650px&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-four/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=dSK-MW-zuAc&quot;&gt;Coding the Hilbert Curve&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marcin-chwedczuk/hilbert_curve&quot;&gt;One file JS program that draws Hilbert curve iteratively&lt;/a&gt; by &lt;a href=&quot;https://github.com/marcin-chwedczuk&quot;&gt;Marcin Chwedczuk&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=3s7h2MHQtxc&amp;amp;pp=0gcJCR0AztywvtLA&quot;&gt;Hilbert&#39;s Curve: Is infinite math useful?&lt;/a&gt; by &lt;a href=&quot;https://www.youtube.com/@3blue1brown&quot;&gt;3Blue1Brown&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

        </content>
      </entry>
      <entry>
        <title>Learning p5.js week three: Wave Function Collapse</title>
        <link href="https://gary.place/blog/learning-p5-js-week-three/"/>
        <updated>2025-10-26T00:00:00Z</updated>
        <id>https://gary.place/blog/learning-p5-js-week-three/</id>
        <summary type="html">
          Week three of learning p5.js: implementing the Wave Function Collapse algorithm in JavaScript to generate procedural patterns and explore constraint-based systems.
        </summary>
        <content type="html">
          &lt;p&gt;After a short break, this week I’m exploring the &lt;strong&gt;Wave Function Collapse&lt;/strong&gt; algorithm, following &lt;a href=&quot;https://www.youtube.com/watch?v=rI_y2GAlQFM&quot;&gt;this video&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-wave-function-collapse&quot; tabindex=&quot;-1&quot;&gt;What Is Wave Function Collapse &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-three/#what-is-wave-function-collapse&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Model_synthesis&quot;&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] a family of &lt;a href=&quot;https://en.wikipedia.org/wiki/Constraint_solving&quot; title=&quot;Constraint solving&quot;&gt;constraint-solving&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Algorithm&quot; title=&quot;Algorithm&quot;&gt;algorithms&lt;/a&gt; commonly used in &lt;a href=&quot;https://en.wikipedia.org/wiki/Procedural_generation&quot; title=&quot;Procedural generation&quot;&gt;procedural generation&lt;/a&gt;, especially in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Video_game_industry&quot; title=&quot;Video game industry&quot;&gt;video game industry&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In simpler terms, it’s a system that generates patterns or terrains based on rules: it decides what can go where, making sure every new tile fits the constraints of its neighbors.&lt;/p&gt;
&lt;p&gt;It’s often used in procedural world generation for games, where randomness needs to feel intentional.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How It Works &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-three/#how-it-works&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The sketch creates a grid of cells across the canvas and assigns a tile to each one.&lt;/p&gt;
&lt;p&gt;Every time a new tile is placed, it checks the constraints of its neighbors and narrows down the possible options. If more than one tile fits, it picks one at random while balancing order and chance.&lt;/p&gt;
&lt;p&gt;What emerges is a patchwork of tiles that looks structured yet organic, as if the design grew naturally from a set of invisible rules.&lt;/p&gt;
&lt;h2 id=&quot;final-result&quot; tabindex=&quot;-1&quot;&gt;Final Result &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-three/#final-result&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;iframe src=&quot;https://editor.p5js.org/KuluGary/full/bsDgOBTKQ&quot; height=&quot;350px&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Here’s the sketch in motion. Watch how the grid resolves itself into a coherent pattern.&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-three/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rI_y2GAlQFM&quot;&gt;Coding Challenge 171: Wave Function Collapse&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mxgmn/WaveFunctionCollapse&quot;&gt;Wave Function Collapse algorithm&lt;/a&gt; by &lt;a href=&quot;https://github.com/mxgmn&quot;&gt;Maxim Gumin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://paulmerrell.org/model-synthesis/&quot;&gt;Model Synthesis&lt;/a&gt; by &lt;a href=&quot;https://paulmerrell.org/&quot;&gt;Paul Merrell&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discourse.processing.org/t/wave-collapse-function-algorithm-in-processing/12983&quot;&gt;“Wave Function Collapse” in Processing&lt;/a&gt; at &lt;a href=&quot;https://discourse.processing.org/&quot;&gt;The Processing Foundation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

        </content>
      </entry>
      <entry>
        <title>Learning p5.js week two: Marching Squares</title>
        <link href="https://gary.place/blog/learning-p5-js-week-two/"/>
        <updated>2025-10-11T00:00:00Z</updated>
        <id>https://gary.place/blog/learning-p5-js-week-two/</id>
        <summary type="html">
          Week two of p5.js experiments: implementing the marching squares algorithm in JavaScript to generate procedural worlds from simple values.
        </summary>
        <content type="html">
          &lt;p&gt;This week&#39;s experiment dives into the &lt;strong&gt;marching squares&lt;/strong&gt; algorithm, following &lt;a href=&quot;https://www.youtube.com/watch?v=0ZONMNUKTfU&quot;&gt;this video&lt;/a&gt; from &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-are-marching-squares&quot; tabindex=&quot;-1&quot;&gt;What are Marching Squares &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-two/#what-are-marching-squares&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://en.wikipedia.org/wiki/Marching_squares&quot;&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] an &lt;a href=&quot;https://en.wikipedia.org/wiki/Algorithm&quot; title=&quot;Algorithm&quot;&gt;algorithm&lt;/a&gt; that generates &lt;a href=&quot;https://en.wikipedia.org/wiki/Contour_lines&quot; title=&quot;Contour lines&quot;&gt;contours&lt;/a&gt; for a two-dimensional &lt;a href=&quot;https://en.wikipedia.org/wiki/Scalar_field&quot; title=&quot;Scalar field&quot;&gt;scalar field&lt;/a&gt; (rectangular &lt;a href=&quot;https://en.wikipedia.org/wiki/Array_data_structure&quot; title=&quot;Array data structure&quot;&gt;array&lt;/a&gt; of individual numerical values)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In simpler terms, it’s a way to turn a field of numbers into shapes.
Its 3D cousin, &lt;a href=&quot;https://en.wikipedia.org/wiki/Marching_cubes&quot;&gt;marching cubes&lt;/a&gt;, is often used in &lt;a href=&quot;https://gary.place/blog/tags/game-dev&quot;&gt;game development&lt;/a&gt; to build procedural worlds — think &lt;a href=&quot;https://www.minecraft.net/&quot;&gt;Minecraft&lt;/a&gt; or &lt;a href=&quot;https://terraria.org/&quot;&gt;Terraria&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re curious about how that looks in practice, &lt;a href=&quot;https://www.youtube.com/watch?v=M3iI2l0ltbE&quot;&gt;Sebastian Lague’s video&lt;/a&gt; is a great deep dive into generating landscapes on the fly.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How It Works &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-two/#how-it-works&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The script builds a grid of cells across the canvas like a chessboard, and assigns each corner a value based on a noise function.&lt;/p&gt;
&lt;p&gt;Each square is then evaluated: depending on which corners are above or below a threshold, a contour line is drawn through it. The result is a fluid, organic network of lines that seem to outline invisible terrain.&lt;/p&gt;
&lt;p&gt;It&#39;s a surprisingly compact algorithm for what it does. With just a few loops and a bit of logic, a world starts to take shape from numbers alone.&lt;/p&gt;
&lt;h2 id=&quot;final-result&quot; tabindex=&quot;-1&quot;&gt;Final result &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-two/#final-result&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;iframe src=&quot;https://editor.p5js.org/KuluGary/full/zzYneA4wT&quot; height=&quot;350px&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-two/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=0ZONMNUKTfU&quot;&gt;Coding Marching Squares&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/joshforisha/open-simplex-noise-js&quot;&gt;open-simplex-noise-js&lt;/a&gt; by &lt;a href=&quot;https://github.com/joshforisha&quot;&gt;Josh Forisha&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

        </content>
      </entry>
      <entry>
        <title>Learning p5.js week one: Paper marbling algorithm</title>
        <link href="https://gary.place/blog/learning-p5-js-week-one/"/>
        <updated>2025-10-04T00:00:00Z</updated>
        <id>https://gary.place/blog/learning-p5-js-week-one/</id>
        <summary type="html">
          Week one of p5.js experiments: a digital take on paper marbling, where geometry and color mix into flowing, unpredictable patterns.
        </summary>
        <content type="html">
          &lt;p&gt;This week starts my first experiment with &lt;a href=&quot;https://p5js.org/&quot;&gt;p5.js&lt;/a&gt;. The subject for today is the paper marbling algorithm, based in the implementation explained in &lt;a href=&quot;https://www.youtube.com/watch?v=p7IGZTjC008&quot;&gt;this video&lt;/a&gt; by &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Paper marbling, as defined by &lt;a href=&quot;https://en.wikipedia.org/wiki/Paper_marbling&quot;&gt;Wikipedia&lt;/a&gt;, is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] a method of &lt;a href=&quot;https://en.wikipedia.org/wiki/Aqueous_solution&quot; title=&quot;Aqueous solution&quot;&gt;aqueous&lt;/a&gt; surface design, which can produce patterns similar to smooth &lt;a href=&quot;https://en.wikipedia.org/wiki/Marble&quot; title=&quot;Marble&quot;&gt;marble&lt;/a&gt; or other kinds of &lt;a href=&quot;https://en.wikipedia.org/wiki/Stone&quot; title=&quot;Stone&quot;&gt;stone&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this context, the algorithm is a way to recreate that effect digitally: a simulation of ink swirling across a virtual surface.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How It Works &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-one/#how-it-works&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To bring that idea into code, the script simulates the process of dropping colored ink onto a virtual surface and shaping it into marbled patterns. Each ink drop is represented by a set of vertices forming a circle. When new drops interact, they distort each other using a geometric transformation that mimics how fluids displace ink on water.&lt;/p&gt;
&lt;p&gt;Dragging the mouse adds &amp;quot;tines&amp;quot;, brush-like waves that pull and stretch the surface in a given direction. The deformation strength fades with distance, creating the rippled lines typical of marbled paper.&lt;/p&gt;
&lt;p&gt;All the parameters (color palette, drop sizes, and deformation strength) can be tweaked through simple UI elements. It&#39;s a surprisingly compact sketch for how organic the results look.&lt;/p&gt;
&lt;h2 id=&quot;final-result&quot; tabindex=&quot;-1&quot;&gt;Final result &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-one/#final-result&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;iframe src=&quot;https://editor.p5js.org/KuluGary/full/cjTCcIybY&quot; height=&quot;720px&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-one/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=p7IGZTjC008&quot;&gt;Coding Challenge 183: Paper Marbling Algorithm&lt;/a&gt; by &lt;a href=&quot;https://www.youtube.com/@TheCodingTrain&quot;&gt;The Coding Train&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.amandaghassaei.com/2022/10/25/digital-marbling/&quot;&gt;Digital Marbling&lt;/a&gt; by &lt;a href=&quot;https://amandaghassaei.com/&quot;&gt;Amanda Ghassaei&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://people.csail.mit.edu/jaffer/Marbling/&quot;&gt;Mathematical Marbling&lt;/a&gt; by &lt;a href=&quot;https://people.csail.mit.edu/jaffer/&quot;&gt;Audrey Jaffer&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

        </content>
      </entry>
      <entry>
        <title>Learning p5.js week zero: Introduction</title>
        <link href="https://gary.place/blog/learning-p5-js-week-zero/"/>
        <updated>2025-10-01T00:00:00Z</updated>
        <id>https://gary.place/blog/learning-p5-js-week-zero/</id>
        <summary type="html">
          It&#39;s been a while since I wanted to learn p5.js to make art through code, and in the next few weeks I&#39;m going to be sharing my progress with this library.
        </summary>
        <content type="html">
          &lt;p&gt;A few years back I stumbled onto &lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Daniel_Shiffman&quot;&gt;Daniel Shiffman&lt;/a&gt;, and ended up watching a handful of his videos on &lt;a href=&quot;https://www.youtube.com/c/TheCodingTrain/&quot;&gt;YouTube&lt;/a&gt;. That&#39;s where I first came across &lt;a href=&quot;https://p5js.org/&quot;&gt;p5.js&lt;/a&gt;, an open-source JavaScript library built on top of &lt;a href=&quot;https://en.wikipedia.org/wiki/Processing&quot;&gt;Processing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;ve been developing software for a while, but I&#39;ve never used JavaScript in the context of visual and interactive art. p5.js looked like the perfect excuse to try.&lt;/p&gt;
&lt;p&gt;So this is a little learning log. I&#39;ll be posting sketches and notes about the journey here on the journal.&lt;/p&gt;
&lt;h2 id=&quot;references-and-tools&quot; tabindex=&quot;-1&quot;&gt;References and tools &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/learning-p5-js-week-zero/#references-and-tools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are the main references I&#39;ll be leaning on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://thecodingtrain.com/&quot;&gt;The Coding Train website&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/c/TheCodingTrain/&quot;&gt;YouTube channel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://p5js.org/reference/&quot;&gt;p5.js documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://editor.p5js.org/&quot;&gt;p5.js web editor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://www.npmjs.com/package/p5&quot;&gt;p5 npm package&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you&#39;re curious about the full setup I&#39;m using, you can check out my &lt;a href=&quot;https://gary.place/uses#software&quot;&gt;/uses page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for following along on this sidequest. Let&#39;s see where it goes.&lt;/p&gt;

        </content>
      </entry>
      <entry>
        <title>Integrating Chattable as a Guestbook</title>
        <link href="https://gary.place/blog/integrating-chattable-as-a-guestbook/"/>
        <updated>2025-09-16T00:00:00Z</updated>
        <id>https://gary.place/blog/integrating-chattable-as-a-guestbook/</id>
        <summary type="html">
          I wanted to add a /guestbook page into this site for a while and for that I chose Chattable.
        </summary>
        <content type="html">
          &lt;p&gt;It&#39;s pretty common in the indie web to have a &lt;a href=&quot;https://gary.place/guestbook&quot;&gt;/guestbook&lt;/a&gt;, a little corner where visitors can leave a message for others to read. I&#39;d been meaning to add one for a while, but I wanted something that felt native to the site: no external service pages, and styling that wouldn&#39;t clash with the rest of the site.&lt;/p&gt;
&lt;h2 id=&quot;integrating-chattable&quot; tabindex=&quot;-1&quot;&gt;Integrating Chattable &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-chattable-as-a-guestbook/#integrating-chattable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://iframe.chat/&quot;&gt;Chattable&lt;/a&gt; turned out to be a nice fit. Once you make an account, it gives you an iframe snippet that drops straight into your page. I just created a &lt;a href=&quot;https://gary.place/guestbook&quot;&gt;/guestbook&lt;/a&gt; route, tossed the snippet into it, and initialized Chattable with my own stylesheet:&lt;/p&gt;
&lt;pre class=&quot;language-html &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  chattable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;stylesheet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/css/chattable.css&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was basically it, the guestbook showed up right away.&lt;/p&gt;
&lt;h2 id=&quot;theming-the-iframe&quot; tabindex=&quot;-1&quot;&gt;Theming the iframe &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-chattable-as-a-guestbook/#theming-the-iframe&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Styling iframes is usually a headache, but Chattable has a nice escape hatch: you can pass in a single CSS file during initialization.&lt;/p&gt;
&lt;p&gt;The trickier part was theming. My site uses a data attribute on the &lt;html&gt; element to store user preferences: theme, font stack, font size... But the iframe doesn&#39;t inherit those. I couldn&#39;t just poke into its DOM and patch things up.&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;My solution: pre-generate a CSS file for every possible combination of preferences and point Chattable to the right one.&lt;/p&gt;
&lt;p&gt;In practice that means: &lt;code&gt;theme × font-stack × font-size&lt;/code&gt; which generates NaN CSS files.&lt;/p&gt;
&lt;p&gt;Not something I&#39;d ever do by hand. Instead, I added a &lt;code&gt;beforeBuild&lt;/code&gt; function in Eleventy that spits them out for me. Whenever I change a theme file, the CSS set rebuilds and the guestbook stays in sync with the rest of the site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gary.place/blog/integrating-chattable-as-a-guestbook/assets/01.png&quot; alt=&quot;CSS File generation diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can check out the full implementation &lt;a href=&quot;https://github.com/KuluGary/website/blob/master/src/js/11ty/generic.js#L250&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then I just needed to point Chattable to the correct CSS file:&lt;/p&gt;
&lt;pre class=&quot;language-html &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matchMedia &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;(prefers-color-scheme: dark)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dark-blue&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;grayscale&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stack &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fontStack &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system-ui&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fontSize &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standard&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  chattable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;stylesheet&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/css/chattable/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;theme&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;stack&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;size&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.css&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;handling-required-javascript&quot; tabindex=&quot;-1&quot;&gt;Handling required Javascript &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-chattable-as-a-guestbook/#handling-required-javascript&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The guestbook won&#39;t run without JavaScript. That&#39;s fine, but I still wanted to handle it gracefully.&lt;/p&gt;
&lt;p&gt;By default, I hide the guestbook:&lt;/p&gt;
&lt;pre class=&quot;language-css &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;#chattable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I only show it if JS is available:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;#chattable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And for folks with JS disabled, I added a fallback message with &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-html &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;noscript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  This guestbook is provided by &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://iframe.chat/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Chattable&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;, and requires JavaScript to run. If you
  want to leave a comment enable Javascript, or &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/contact&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;contact me through other channels&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;!
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;noscript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the page still makes sense even if the interactive part can&#39;t load.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping up &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-chattable-as-a-guestbook/#wrapping-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Getting the guestbook up and running wasn&#39;t hard. The bigger challenge was making sure it blended in with the rest of the site. Chattable handled most of the heavy lifting, and a little build-script trickery took care of the theming edge cases.&lt;/p&gt;
&lt;p&gt;For now, it feels like a natural extension of the site, not an add-on. Which is exactly what I wanted.&lt;/p&gt;
&lt;p&gt;So the &lt;a href=&quot;https://gary.place/guestbook&quot;&gt;guestbook&lt;/a&gt;&#39;s open. Leave a note, a doodle, a hello. It&#39;s all part of the fun.&lt;/p&gt;

        </content>
      </entry>
      <entry>
        <title>Applying authentication architecture</title>
        <link href="https://gary.place/blog/applying-authentication-architecture/"/>
        <updated>2025-09-13T00:00:00Z</updated>
        <id>https://gary.place/blog/applying-authentication-architecture/</id>
        <summary type="html">
          To scale user access permissions based on role and attribute, we implemented ABAC permissions into our front-end application.
        </summary>
        <content type="html">
          &lt;p&gt;Every app with authentication eventually needs to tackle authorization. In our case, once our product grew from a small experiment into a mid-sized application, it became clear we needed a proper model to keep users from doing things they shouldn&#39;t.&lt;/p&gt;
&lt;p&gt;To make the idea more concrete, let&#39;s imagine a simple blog with multiple authors and readers. Here&#39;s how I explored different approaches.&lt;/p&gt;
&lt;h2 id=&quot;the-naive-approach&quot; tabindex=&quot;-1&quot;&gt;The naïve approach &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/applying-authentication-architecture/#the-naive-approach&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When we built our MVP, we started with the obvious: inline role checks.&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;submitUserPostEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;editor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// submit the code to the back-end&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or inside a component:&lt;/p&gt;
&lt;pre class=&quot;language-jsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;textarea&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
	&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;editor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
		&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;edit-post-button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
	)}
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s fast, easy, and it works. But the problem shows up later: these conditions are scattered through business logic and UI. When the app grows and a permission needs to change, you have to hunt down every conditional buried in the code.&lt;/p&gt;
&lt;p&gt;So, the first improvement is evident: centralize the rules.&lt;/p&gt;
&lt;h2 id=&quot;role-based-access-control&quot; tabindex=&quot;-1&quot;&gt;Role-based access control &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/applying-authentication-architecture/#role-based-access-control&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the blog example, we can define a few concepts: subjects (users), roles, permissions, and actions. Role-based access control (RBAC) links them together: each user gets one or more roles, and each role comes with a set of permissions that decide which actions are allowed.&lt;/p&gt;
&lt;p&gt;Here&#39;s a minimal setup:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PERMISSIONS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;create:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;update:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;view:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;delete:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;editor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;update:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;view:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;view:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a simple checker:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;hasPermission&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; permission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PERMISSIONS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;role&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;permission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which we can use like this:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// User should be like { id: &quot;a&quot;, roles: [&quot;editor&quot;] }&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;update:post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This already improves things: no more hardcoded checks sprinkled across the codebase. But RBAC still has limits. It doesn&#39;t handle nuanced cases where context matters.&lt;/p&gt;
&lt;h2 id=&quot;attribute-based-access-control&quot; tabindex=&quot;-1&quot;&gt;Attribute-based access control &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/applying-authentication-architecture/#attribute-based-access-control&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What if editors can only update their own posts? Or if readers shouldn&#39;t be able to see posts tagged as drafts? Roles alone can&#39;t answer those questions.&lt;/p&gt;
&lt;p&gt;Attribute-based access control (ABAC) expands the model by considering more than just roles. It factors in attributes of the subject (user), the object (post), the environment, and the policy itself.&lt;/p&gt;
&lt;p&gt;Here&#39;s a sketch:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PERMISSIONS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;posts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;editor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function-variable function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;authorId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now permissions can be a boolean or a callback. The &lt;code&gt;hasPermission&lt;/code&gt; function just needs to handle both cases:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resource&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;role&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; permission &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PERMISSIONS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;role&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;resource&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;permission &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; permission &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;boolean&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; permission&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And usage looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;posts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;update&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With ABAC, we get flexibility. Whether the condition is based on user IDs, tags, dates, or environment variables, the rules live in one place and can scale with the application.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/applying-authentication-architecture/#next-steps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What we&#39;ve built so far is already maintainable and expressive, but there&#39;s room to grow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Centralize permissions across the stack. Right now, these checks only exist in the front-end. Moving them server-side (or sharing them between client and server) would strengthen security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Outsource permission management. Instead of living in a JSON file, permissions could be stored in a dashboard or policy service so non-developers can adjust them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use a library. Rolling your own system is a great learning exercise, but in production a library like &lt;a href=&quot;https://casl.js.org/v6/en&quot;&gt;CASL&lt;/a&gt; would decrease maintenance time and delegate new features.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;final-words&quot; tabindex=&quot;-1&quot;&gt;Final words &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/applying-authentication-architecture/#final-words&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That&#39;s where I landed for now. It&#39;s not the final word on permissions, but it&#39;s a pattern that feels maintainable without being over-engineered. Maybe in a year I&#39;ll swap it all out for something else, but that&#39;s half the fun of keeping a log like this.&lt;/p&gt;

        </content>
      </entry>
      <entry>
        <title>My first steps with Storybook</title>
        <link href="https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/"/>
        <updated>2025-08-23T00:00:00Z</updated>
        <id>https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/</id>
        <summary type="html">
          For a long time I wanted to deepen my knowledge of Storybook, and thanks to a series of needs at work I finally had the chance.
        </summary>
        <content type="html">
          &lt;p&gt;At our startup, UI testing was minimal. This was fine during MVP, but as the platform grew in size and userbase, so did the need for increased maintainability. A few weeks ago I remembered this &lt;a href=&quot;https://kevinyank.com/posts/help-storybook-is-eating-all-our-tests/&quot;&gt;talk by Kevin Yank&lt;/a&gt; where he describes how Culture Amp uses Storybook for nearly every test case.&lt;/p&gt;
&lt;p&gt;During this last holiday season, my project manager scheduled a couple of weeks to tackle technical debt, and one of the things I had to do was implement Storybook into our existing codebase.&lt;/p&gt;
&lt;h2 id=&quot;what-is-storybook&quot; tabindex=&quot;-1&quot;&gt;What is Storybook &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#what-is-storybook&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can think of Storybook as a sandbox where each component lives independently from each other. Instead of lauching a whole Next.js app to test some UI components, you can preview them and tweak them in isolation.&lt;/p&gt;
&lt;h2 id=&quot;adding-storybook-to-a-turborepo&quot; tabindex=&quot;-1&quot;&gt;Adding Storybook to a Turborepo &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#adding-storybook-to-a-turborepo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We chose Turborepo to manage our apps since we needed a shared ecosystem of components, hooks, API calls and providers. Turborepo gave us scalability with a &lt;code&gt;packages/ui&lt;/code&gt; folder for shared UI elements and &lt;code&gt;packages/lib&lt;/code&gt; for API configs, integrations and utils.&lt;/p&gt;
&lt;p&gt;We set up Storybook as a new Turborepo app (&lt;code&gt;apps/storybook&lt;/code&gt;) alongside our two main Next.js ones. This kept configurations isolated while letting us import from &lt;code&gt;packages&lt;/code&gt; to build our Stories.&lt;/p&gt;
&lt;p&gt;Our first Story was for the &lt;code&gt;Button&lt;/code&gt; primitive component:&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Meta&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; StoryObj &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@storybook/nextjs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Button &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@platform/ui/primitives/button&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Primitives/Button&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  component&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Button&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  parameters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; layout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;centered&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  tags&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;autodocs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  argTypes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    variant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      control&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;select&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;variantOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      defaultValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    size&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      control&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;select&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sizeOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      defaultValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  args&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    variant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    size&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    children&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Button&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; satisfies Meta&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; Button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; meta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Story&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StoryObj&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; meta&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Playground&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Story &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  args&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; children&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Button&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;adding-nextauth-support&quot; tabindex=&quot;-1&quot;&gt;Adding NextAuth support &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#adding-nextauth-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many of our components depend on user state, and without NextAuth they would break or show meaningless output. We solved this by setting up a custom session privder for Storybook:&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unauthenticatedSession &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@storybook/mocks/session&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; SessionContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next-auth/react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NextAuthDecorator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PropsWithChildren&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sessionData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; unauthenticatedSession&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SessionContext.Provider&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;‎&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; sessionData &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;authenticated&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SessionContext.Provider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this decorator, we can preview components from different user POVs. For example, our &lt;strong&gt;PlanCard&lt;/strong&gt; shows different available actions for admins vs. users, so we can swap sessions instantly without creating a new test account for each user type.&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; NextAuthDecorator &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@storybook/decorators/NextAuthDecorator&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sessionOptions &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@storybook/mocks/session&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; AddServiceButton &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@platform/ui/components/projects/business-add-service/AddServiceButton&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Components/Project/Creation/Services/AddServiceButton&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  component&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddServiceButton&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  argTypes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    session&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      control&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;select&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      mapping&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; sessionOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Default&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;keyof&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; sessionOptions &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NextAuthDecorator&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AddServiceButton&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NextAuthDecorator&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;adding-add-ons-to-storybook&quot; tabindex=&quot;-1&quot;&gt;Adding add-ons to Storybook &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#adding-add-ons-to-storybook&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once the technical setup was done, I focused back on what I wanted to achieve in the first place: easier design collacoration and testing business logic in isolation.&lt;/p&gt;
&lt;h3 id=&quot;design-collaboration&quot; tabindex=&quot;-1&quot;&gt;Design collaboration &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#design-collaboration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using the &lt;code&gt;@storybook/addon-designs&lt;/code&gt;, we can link and display Figma files directly inside Storybook. Our designers can now review UI compliance from a single interface:&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Playground&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Story &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  args&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; children&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Button&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  parameters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    design&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;figma&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://www.figma.com/design/...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;data-api-simulation&quot; tabindex=&quot;-1&quot;&gt;Data &amp;amp; API simulation &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#data-api-simulation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many of our components depend on backend data. In Storybook, pointing to live APIs is brittle and slow and takes away control over the data we want to perform our tests with. Instead, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provide React Query context&lt;/strong&gt; with a global &lt;code&gt;QueryClientProvider&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; QueryClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; QueryClientProvider &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@tanstack/react-query&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; queryClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueryClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  defaultOptions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    queries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; retry&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decorators &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Story&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;QueryClientProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;queryClient&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Story&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;QueryClientProvider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mock backend calls&lt;/strong&gt; using Mock Service Worker (MSW).&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; http &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;msw&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unauthenticatedSession &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@storybook/mocks/session&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; authHandlers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/auth/session&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; HttpResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unauthenticatedSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, enable it globally in &lt;code&gt;preview.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; initialize&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mswLoader &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;msw-storybook-addon&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; authHandlers &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../src/mocks/handlers/authHandlers&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parameters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  msw&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    handlers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;authHandlers&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; loaders &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;mswLoader&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This let us simulate responses (e.g., logged-in user vs. error state) and test components without touching real APIs.&lt;/p&gt;
&lt;h2 id=&quot;adding-internationalization&quot; tabindex=&quot;-1&quot;&gt;Adding internationalization &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#adding-internationalization&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our app relies heavily on &lt;code&gt;next-intl&lt;/code&gt;. Without i18n, Storybook would render keys like &lt;code&gt;auth.login.button&lt;/code&gt; instead of translated text. The fix was to reuse the same 18n provider we use on our apps inside Storybook:&lt;/p&gt;
&lt;pre class=&quot;language-tsx &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; LocaleProvider &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@platform/lib/client-only/providers/locale-provider.storybook&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; preview&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Preview &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  decorators&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Story&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LocaleProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;es&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;QueryClientProvider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;queryClient&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Story&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;QueryClientProvider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;LocaleProvider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now components display actual strings (&amp;quot;Iniciar sesión&amp;quot; / &amp;quot;Login&amp;quot;), making design reviews realistic.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/integrating-storybook-into-nextjs-turborepo/#next-steps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Storybook began as a way to preview components in isolation, but it&#39;s quickly becoming our UI hub. Next up:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Interaction testing&lt;/strong&gt; – script user flows (e.g., form validation) with Storybook&#39;s testing utilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accessibility checks&lt;/strong&gt; – add &lt;code&gt;@storybook/addon-a11y&lt;/code&gt; to catch accessibility issues early.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Living documentation&lt;/strong&gt; – use &lt;code&gt;@storybook/addon-docs&lt;/code&gt; to make stories double as dev &amp;amp; design docs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even though the team has shifted to other features, Storybook is now part of my daily workflow. I prototype in Storybook first, which makes development smoother, reviews clearer, and collaboration more inclusive.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If you&#39;re working in a startup or small team, Storybook can be a game-changer. Instead of juggling staging builds, backend mocks, and screenshots, you get a single space where components come to life.&lt;/p&gt;
&lt;p&gt;Try setting up a single component in Storybook this week and you&#39;ll see the benefits immediately.&lt;/p&gt;

        </content>
      </entry>
      <entry>
        <title>I added webmentions to my site</title>
        <link href="https://gary.place/blog/i-added-webmentions-to-my-site/"/>
        <updated>2025-06-02T00:00:00Z</updated>
        <id>https://gary.place/blog/i-added-webmentions-to-my-site/</id>
        <summary type="html">
          After looking for ways to add interactivity with my blog posts, I decided to add Webmentions to my site.
        </summary>
        <content type="html">
          &lt;p&gt;Something that is usually seen in any kind of blog is a way to interact with each post. Usually you can leave comments, but there are also ways to track likes and shares in social media. Some website authors use a ready-to-use service like &lt;a href=&quot;https://disqus.com/&quot;&gt;Disqus&lt;/a&gt;, an automated third-party system &lt;a href=&quot;https://www.aleksandrhovhannisyan.com/blog/static-site-comments-github-issues/&quot;&gt;Netlify functions&lt;/a&gt;, or something handmade with a custom database.&lt;/p&gt;
&lt;p&gt;This website is SSG&#39;d (static-site generated), by which I mean that all the content is pre-built and my hosting service serves raw HTML files. In order to fit the what I want from this website, whatever solution I came up with had to match certain requirements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It had to give me full control of how to display the interactions,&lt;/li&gt;
&lt;li&gt;It should be a free and/or open-source solution&lt;/li&gt;
&lt;li&gt;It should not require the user to login into this site or provide any information to me&lt;/li&gt;
&lt;li&gt;It should be simple, unobtrusive and optional&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To fulfill all these requirements, I started to look around the web. Enter &lt;a href=&quot;https://en.wikipedia.org/wiki/Webmention&quot;&gt;Webmentions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;webmentions&quot; tabindex=&quot;-1&quot;&gt;Webmentions &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site/#webmentions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Paraphrasing W3C, webmentions are a way for a website to be notified whenever another site links to it –hence, &lt;em&gt;web mentions&lt;/em&gt;. In this way, instead of having a centralized service to keep track of interactions, it allows a &lt;a href=&quot;https://en.wikipedia.org/wiki/Federation_(information_technology)&quot;&gt;federated&lt;/a&gt; approach in which instead of forcing the user to interact with my platform, I simply get notified whenever they do it in any external site.&lt;/p&gt;
&lt;h3 id=&quot;webmention-io&quot; tabindex=&quot;-1&quot;&gt;Webmention io &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site/#webmention-io&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are a lot of ways to implement webmentions, but as far as I&#39;ve seen a top contender is &lt;a href=&quot;https://webmention.io/&quot;&gt;webmention.io&lt;/a&gt;; a simple hosted service to receive webmentions from anywhere, with an API to retrieve these webmentions and showcase them in your site however you like.&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&quot;https://webmention.io/&quot;&gt;webmention.io&lt;/a&gt; is a webmention receiver, this should work fine if my site is shared by someone who has webmentions integrated in their site. The only problem is that in this day and age, interaction mostly happens through social media and most of them don&#39;t use the webmentions protocol.&lt;/p&gt;
&lt;p&gt;For that, there&#39;s a service called &lt;a href=&quot;https://brid.gy/&quot;&gt;brid.gy&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;bridgy&quot; tabindex=&quot;-1&quot;&gt;Bridgy &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site/#bridgy&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://brid.gy/&quot;&gt;brid.gy&lt;/a&gt; monitors any linked social media to see who interacts with your site. If I share a link to this article in &lt;a href=&quot;https://bsky.app/&quot;&gt;Bluesky&lt;/a&gt; and someone replies, likes or reposts, &lt;a href=&quot;https://brid.gy/&quot;&gt;brid.gy&lt;/a&gt; would send a webmention to &lt;a href=&quot;https://webmention.io/&quot;&gt;webmention.io&lt;/a&gt;, which I could later fetch with their API and showcase it in my site.&lt;/p&gt;
&lt;h3 id=&quot;fetching-my-webmentions&quot; tabindex=&quot;-1&quot;&gt;Fetching my webmentions &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site/#fetching-my-webmentions&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once &lt;a href=&quot;https://brid.gy/&quot;&gt;brid.gy&lt;/a&gt; and &lt;a href=&quot;https://webmention.io/&quot;&gt;webmention.io&lt;/a&gt; are sending and receiving webmentions respectively, I then can bring them into my site and handle them as I see fit. Since this site is made with &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;11ty&lt;/a&gt;, I can retrieve my webmentions in a &lt;a href=&quot;https://www.11ty.dev/docs/data-global/&quot;&gt;global data file&lt;/a&gt; and have access to them in any of my templates.&lt;/p&gt;
&lt;p&gt;To do this I just need to do a simple authorized HTTP Request.&lt;/p&gt;
&lt;pre class=&quot;language-js &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://webmention.io/api/mentions.jf2?token=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WEBMENTIONS_TOKEN&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;amp;per-page=1000&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; webmentions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; webmentions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With my webmentions available as JSON files, I can hanle them and display them however I want. If you wanna see how they are working, you can check them in &lt;a href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site#post-footer&quot;&gt;this article&#39;s footer&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-njk &quot; style=&quot;counter-reset: linenumber 0&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;reposts&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;mentions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webmentionsByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;repost-of&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;filterOwnWebmentions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;likes&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;mentions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webmentionsByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;like-of&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;filterOwnWebmentions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;replies&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;mentions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;webmentionsByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;in-reply-to&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;repostsSize&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;reposts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;likesSize&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;likes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;repliesSize&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;replies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;interactions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;likes&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mergeArrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;reposts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;# &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; #&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;social-media&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;likesSize&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;# &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; #&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;likesSize&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;repostsSize&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;# &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; #&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;repostsSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;repliesSize&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;# &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; #&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;repliesSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;share-buttons&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;components/share-buttons.html&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site/#next-steps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The main problem remaining with my approach to webmentions is the fact that they will only be obtained whenever I choose to rebuild and deploy a new version of the site. The easiest way to fix this may be to configure some sort of cron job to periodically rebuild my site and deploy it, which would also refetch any webmention I may have available.&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot; tabindex=&quot;-1&quot;&gt;Further reading &lt;a class=&quot;anchor-link&quot; href=&quot;https://gary.place/blog/i-added-webmentions-to-my-site/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There were a few articles I used to help me implement these webmentions. Here they are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rknight.me/blog/adding-webmentions-to-your-site/&quot;&gt;Adding Webmentions to Your Site&lt;/a&gt; by Robb Knight.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jay.gooby.org/2021/01/15/getting-webmentions-and-bridgy-working&quot;&gt;Getting web mentions and brid.gy working on the blog &lt;/a&gt; by Jay Caines-Gooby&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bobmonsour.com/blog/adding-webmentions-to-my-site/&quot;&gt;Adding webmentions to my site&lt;/a&gt; by Bob Monsour&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://indiewebify.me/#send-webmentions&quot;&gt;Add the ability to send Webmentions to other IndieWeb sites&lt;/a&gt; by IndieWebify.Me&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://janmonschke.com/adding-webmentions-to-your-static-blog/&quot;&gt;Adding webmentions to your static blog&lt;/a&gt; by Jan Monschke&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sia.codes/posts/webmentions-eleventy-in-depth/&quot;&gt;An In-Depth Tutorial of Webmentions + Eleventy&lt;/a&gt; by Sia Karamalegos&lt;/li&gt;
&lt;/ul&gt;

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