<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/feed.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>All about Ken Hawkins</title>
  <subtitle>Digital Platforms &amp; Audience Engagement Lead who translates between executives, developers, and users to build measurable public-facing systems, and the structured content and data that make them work for people and machines alike. 20+ years delivering results for UN, science, and media organizations.</subtitle>
  <link href="https://www.allaboutken.com/rss.xml" rel="self"/>
  <link href="https://www.allaboutken.com"/>
  <updated>2026-08-08T00:00:00.000Z</updated>
  <id>https://www.allaboutken.com/</id>
  <logo>https://www.allaboutken.com/assets/ken-favicon/assets/android-chrome-512x512.png</logo>
  <icon>https://www.allaboutken.com/assets/ken-favicon/assets/favicon-32x32.png</icon>
  <author>
    <name>Ken Hawkins</name>
    <email>khawkins98@gmail.com</email>
  </author>
  <entry>
    <title>A fresh coat of paint and making information actionable for 24 years</title>
    <link href="https://www.allaboutken.com/posts/20260808-making-information-actionable-for-24-years/"/>
    <published>2026-08-08T00:00:00.000Z</published>
    <updated>2026-08-08T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260808-making-information-actionable-for-24-years/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="career reflection" />
    <category term="data visualization" />
    <category term="static sites" />
    <category term="content strategy" />
    <summary type="html">From early 2000s information design to semantic maps in 2026, the tools changed; my interest in how information connects did not.</summary>
    <content type="html">
&lt;p&gt;Over the last months, I&#39;ve been reflecting on my career, how I position what it is I do and how I use this site to communicate it. (&amp;quot;Person who is good at translating between technical and non-technical stakeholders&amp;quot; is a useful skill, but not what I do, nor a marketable term.) I decided to scratch that itch by looking through some of my old online presences, the websites and the photo archives, expecting mostly nostalgia. But I found new clarity on the same question I work on now: how should information be arranged so someone can find it, understand it and connect it to something else?&lt;/p&gt;
&lt;p&gt;The record I can recover runs from 2002 to 2026. The tools changed from static HTML and Flash to content models, semantic embeddings and server-rendered SVG. Reading it back gave this blog a structure it has always needed.&lt;/p&gt;
&lt;h2 id=&quot;brace-yourself-for-some-meditative-navel-gazing&quot; tabindex=&quot;-1&quot;&gt;Brace yourself for some meditative navel gazing&lt;a href=&quot;#brace-yourself-for-some-meditative-navel-gazing&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My first website was a GeoCities page I made in high school around 1997. I may never find it again. My memory is that I first edited HTML in HotDog, a Windows editor &lt;a href=&quot;https://www.webdesignmuseum.org/software/hotdog-1-0-in-1995&quot;&gt;released in 1995&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What I remember is not the page but the reaction. A friend (Melissa, who I don&#39;t think I&#39;ve spoken to since graduation) and I found it genuinely mind-bending that we could make a thing and have it be &lt;em&gt;there&lt;/em&gt;, addressable by anyone, without asking permission or paying a gatekeeper.&lt;/p&gt;
&lt;p&gt;The first substantial site I can recover is Environmental Media. The Wayback Machine kept a &lt;a href=&quot;https://web.archive.org/web/20020120145620/https://envmedia.com/&quot;&gt;2002 snapshot of the site&lt;/a&gt;, which I built in 2001 with search, Flash video and an online shop. The implementation is very much of its time; the interest in helping people navigate different kinds of information is less so.&lt;/p&gt;
&lt;p&gt;An &lt;a href=&quot;https://web.archive.org/web/20021012212136/http://www.stuffedinabox.com/&quot;&gt;October 2002 snapshot of Stuffed in a Box&lt;/a&gt; preserves a mostly &amp;quot;Coming Soon&amp;quot; site for my tiny web-design service. By April 2003, an &lt;a href=&quot;https://web.archive.org/web/20030425093346/http://www.stuffedinabox.com/portfolio/resume/index.htm&quot;&gt;online résumé&lt;/a&gt; listed Stuffed in a Box Design as a self-owned business and used proper em dashes — way before AI killed being a cool kid who knew how to.&lt;/p&gt;
&lt;p&gt;The blog arrived on &lt;a href=&quot;https://web.archive.org/web/20050206010037/http://www.stuffedinabox.com/archives/000004.html&quot;&gt;5 September 2003&lt;/a&gt;. Its first post, &amp;quot;I&#39;ve done and will do,&amp;quot; promised observations about newspapers, infographics, technology, politics and history. A post the following September admitted that it had been nearly a year since the last one, but the intended subjects are recognizable.&lt;/p&gt;
&lt;p&gt;Flickr supplies more breadcrumbs, two of them a month apart. In November 2006 I posted &lt;a href=&quot;https://www.flickr.com/photos/khawkins04/301936689/&quot;&gt;a newspaper page I had laid out&lt;/a&gt;: every school in five South Carolina districts, rated across four years of state report cards.&lt;/p&gt;
&lt;p&gt;South Carolina published words rather than numbers: excellent, good, average, below average, unsatisfactory, for every school, four years running. Words do not stack up. You cannot skim two hundred of them and come away with a shape. Stars you can count, and counting is what a parent looking for their own school actually does.&lt;/p&gt;
&lt;p&gt;A month later I uploaded &lt;a href=&quot;https://www.flickr.com/photos/khawkins04/337710045/&quot;&gt;a close-up of a pinched letterform&lt;/a&gt;: I was already stopping to admire ink traps in 2006. Something I found myself dwelling on again twelve years later, &lt;a href=&quot;https://www.allaboutken.com/posts/20180319-data-font-for-life-sciences.html&quot;&gt;when considering fonts for digital data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next many years would be consumed by &lt;a href=&quot;https://www.TheDigitel.com&quot;&gt;TheDigitel.com&lt;/a&gt;, an excellent if poorly timed idea for hyperlocal news that I co-founded (2008 was bad for the entire economy, advertiser budgets included). I won&#39;t digress into that; it is a full-on post of its own.&lt;/p&gt;
&lt;h2 id=&quot;reading-the-archive-instead-of-scrolling-it&quot; tabindex=&quot;-1&quot;&gt;Reading the archive instead of scrolling it&lt;a href=&quot;#reading-the-archive-instead-of-scrolling-it&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I finally parted ways with TheDigitel in 2011, taking a job offer in Germany, and resurfaced writing on this site in 2013. It now contains roughly a hundred entries written on and off across different jobs, tools and versions of the site. That is a mineable pool: enough to compare topics and surface passages that resemble one another. It is also enough for a conventional reverse-chronological blog to hide most of what is there.&lt;/p&gt;
&lt;p&gt;This 2026 redesign began with typography, graph paper and a more technical datasheet vocabulary. Posts gained document references. The &lt;a href=&quot;https://www.allaboutken.com/archive/&quot;&gt;archive gained a timeline&lt;/a&gt;, while &lt;a href=&quot;https://www.allaboutken.com/directory/#subjects&quot;&gt;topic pages&lt;/a&gt; and small activity charts made it possible to browse by subject rather than only by publication date.&lt;/p&gt;
&lt;article class=&quot;kh-bleed-out&quot;&gt;
  &lt;div class=&quot;kh-compare&quot;&gt;
    &lt;img class=&quot;kh-compare__img&quot; src=&quot;https://www.allaboutken.com/images/blog/oldsite-post-masthead.jpg&quot; alt=&quot;The old head of the same entry: a site navigation bar, a back link, then the title reversed out of a wide yellow band, with the date, word count and a comma-separated list of tags beneath it.&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img class=&quot;kh-compare__img kh-compare__img--after&quot; src=&quot;https://www.allaboutken.com/images/blog/newsite-post-masthead.jpg&quot; alt=&quot;The new head of the same entry: a row of dots marking every month since 2013, a document reference reading Ref AAK-20260330, the title, then the date, word count, a small length gauge, and three topics each with a count.&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span class=&quot;kh-compare__label kh-compare__label--before&quot; aria-hidden=&quot;true&quot;&gt;Before&lt;/span&gt;
    &lt;span class=&quot;kh-compare__label kh-compare__label--after&quot; aria-hidden=&quot;true&quot;&gt;After&lt;/span&gt;
    &lt;span class=&quot;kh-compare__handle&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;
    &lt;input class=&quot;kh-compare__range&quot; type=&quot;range&quot; min=&quot;0&quot; max=&quot;100&quot; value=&quot;50&quot; step=&quot;1&quot; aria-label=&quot;Reveal more of the new layout&quot; /&gt;
  &lt;/div&gt;
  &lt;figcaption class=&quot;kh-text-body--3&quot;&gt;The same entry, before and after. The head now reads like a datasheet: a document reference, a length gauge against the corpus median, and topics with the number of other entries filed under each. Drag to compare, or focus the slider and use the arrow keys.&lt;/figcaption&gt;
&lt;/article&gt;

&lt;p&gt;A blog normally asks, &amp;quot;What did I publish most recently?&amp;quot; A register asks, &amp;quot;What is here, and how does one thing relate to another?&amp;quot;&lt;/p&gt;
&lt;p&gt;The newest entry is not necessarily the best way into the work. A &lt;a href=&quot;https://www.allaboutken.com/posts/20170723-corporate-design-through-ia.html&quot;&gt;2017 post about information architecture&lt;/a&gt; leads more naturally to a &lt;a href=&quot;https://www.allaboutken.com/posts/20260327-content-architecture-shipping-container/&quot;&gt;2026 argument about content delivery&lt;/a&gt; than to whatever happened to be published between them. A &lt;a href=&quot;https://www.allaboutken.com/posts/20180319-data-font-for-life-sciences.html&quot;&gt;type experiment from 2018&lt;/a&gt; makes more sense beside later work on &lt;a href=&quot;https://www.allaboutken.com/posts/20260512-datatype-data-font-inside-out/&quot;&gt;data fonts&lt;/a&gt; and &lt;a href=&quot;https://www.allaboutken.com/posts/20260429-svgomg-font/&quot;&gt;embedded SVG text&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;kh-bleed-out&quot;&gt;
  &lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/#returns&quot;&gt;&lt;img src=&quot;https://www.allaboutken.com/images/blog/newsite-story-lab-returns.jpg&quot; alt=&quot;A chronological arc diagram from 2013 to 2026 over four career bands labeled DRS, EMBL-EBI, EMBL and UNDRR. Tall arcs connect entries at least three years apart; the tallest reach from 2017 and 2018 across to 2025 and 2026, and most marks cluster near 2026.&quot; class=&quot;kh-figure__image&quot; /&gt;&lt;/a&gt;
  &lt;figcaption class=&quot;kh-figure__caption&quot;&gt;The same pairs, drawn. Each tall arc joins two entries at least three years apart: the 2017 information-architecture post reaching the 2026 content-delivery argument, the 2018 data-font experiment reaching later font work. Height is the years between two entries, never strength. &lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/#returns&quot;&gt;The Story Lab&lt;/a&gt; has the interactive version.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;My little demo that I&#39;ve called the &amp;quot;&lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/&quot;&gt;Corpus Story Lab&lt;/a&gt;&amp;quot; tests those readings. One view stacks every topic as its own line, so it is possible to see when each was active and which ones came back. Another view assembles &amp;quot;biographies&amp;quot; from a few editorial anchor posts and the entries closest to them in meaning.&lt;/p&gt;
&lt;figure class=&quot;kh-bleed-out&quot;&gt;
  &lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/&quot;&gt;&lt;img src=&quot;https://www.allaboutken.com/images/blog/newsite-story-lab-joyplot.jpg&quot; alt=&quot;A ridgeline chart on a dark ground: one offset line per topic, stacked from cloud infrastructure at the top down to search at the bottom, each tracing that topic&#39;s activity from 2013 to 2026 with its entry total at the right. Most lines are flat until a spike near 2026; AI totals 19.&quot; class=&quot;kh-figure__image&quot; /&gt;&lt;/a&gt;
  &lt;figcaption class=&quot;kh-figure__caption&quot;&gt;Every topic as its own line, offset and overlapping, after Kevin Marks&#39;s &lt;em&gt;The Joy of Sparks&lt;/em&gt;. Each row is normalized to its own peak, so height compares timing and shape within a row and never volume between rows; the number at the right is that topic&#39;s total in the semantic corpus the lab draws on, which runs smaller than the full &lt;a href=&quot;https://www.allaboutken.com/directory/#subjects&quot;&gt;topic index&lt;/a&gt;. &lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/&quot;&gt;The Story Lab&lt;/a&gt; has the interactive version.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That typography biography currently begins with writing from 2017 and 2018. The Flickr photograph shows why the larger archive matters: the visualization is not wrong, but its corpus is incomplete.&lt;/p&gt;
&lt;figure class=&quot;kh-bleed-out&quot;&gt;
  &lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/#biographies&quot;&gt;&lt;img src=&quot;https://www.allaboutken.com/images/blog/newsite-story-lab-biographies.jpg&quot; alt=&quot;Three stacked biography trails, each a labeled row with a small dotted timeline: &#39;Typography and data&#39;, &#39;Rebuilding and preserving a personal site&#39; and &#39;Reusable systems at organizational scale&#39;. Each row lists its linked entries by year with a similarity score, and every timeline&#39;s marks cluster toward the recent end.&quot; class=&quot;kh-figure__image&quot; /&gt;&lt;/a&gt;
  &lt;figcaption class=&quot;kh-figure__caption&quot;&gt;Three assembled biographies, each a row with its own small timeline. The &quot;Typography and data&quot; trail begins in 2017, so the 2006 ink-trap photograph is nowhere in it; the archive the lab reads only starts in 2013. Notice how every trail&#39;s marks bunch toward the recent end. &lt;a href=&quot;https://www.allaboutken.com/stats/story-lab/#biographies&quot;&gt;The Story Lab&lt;/a&gt; has the interactive version.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;similarity-meaningfully&quot; tabindex=&quot;-1&quot;&gt;Similarity, meaningfully&lt;a href=&quot;#similarity-meaningfully&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This site has a &lt;a href=&quot;https://www.allaboutken.com/posts/20260302-semantic-search-browser-embeddings/&quot;&gt;semantic search index&lt;/a&gt; which I&#39;ve used to connect repeated language and adjacent subject matter. It does not show that one idea caused another, that an employer shaped a topic, or even that I consciously remembered an older post while writing a newer one. It also only knows what I have published here: drafts, navigation and everything off-site never enter it, so its totals sit below the topic counts and the decade before 2013 barely registers. The map is candid about its own gaps, and that turns out to be the useful part.&lt;/p&gt;
&lt;p&gt;Compounding this, much of the meaning I&#39;d like to draw from still lives elsewhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;more than a thousand photographs in &lt;a href=&quot;https://www.flickr.com/photos/khawkins04/&quot;&gt;my Flickr archive&lt;/a&gt;, which should not remain their only home;&lt;/li&gt;
&lt;li&gt;writing published through employers, projects and publications that now lives on sites I do not control;&lt;/li&gt;
&lt;li&gt;small tools and experiments spread across &lt;a href=&quot;https://github.com/khawkins98&quot;&gt;my GitHub repositories&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;older versions of this site preserved unevenly in Git history and the Wayback Machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The role I want for this site is an index across them: photographs, projects and recovered articles, each carrying its date, its original context and a link back to where it first appeared.&lt;/p&gt;
&lt;p&gt;Photography is the part I most want to bring back as an active practice, not only a rescue operation. The current archive is heavily weighted toward words and software.&lt;/p&gt;
&lt;p&gt;The site has been rebuilt several times, each migration about the machinery: &lt;a href=&quot;https://web.archive.org/web/20150725050631/http://work.allaboutken.com/&quot;&gt;Drupal&lt;/a&gt;, &lt;a href=&quot;https://www.allaboutken.com/posts/20170725-site-update.html&quot;&gt;Gulp&lt;/a&gt;, &lt;a href=&quot;https://www.allaboutken.com/posts/20180210-zurb-panini-2-migration.html&quot;&gt;Panini&lt;/a&gt;, &lt;a href=&quot;https://www.allaboutken.com/posts/20200713-moving-from-panini-to-eleventy/&quot;&gt;Eleventy&lt;/a&gt;, then a &lt;a href=&quot;https://www.allaboutken.com/posts/20250831-site-overhaul-eleventy-v3/&quot;&gt;much simpler Eleventy stack&lt;/a&gt;. This rebuild started with a question about myself instead: how to say what I do without reaching for &amp;quot;person who translates between technical and non-technical people,&amp;quot; which is a real skill and a hopeless title.&lt;/p&gt;
&lt;p&gt;I have settled, for now, on Digital Platforms &amp;amp; Audience Engagement Lead, but the longer winded, and truer, version is the through line the record keeps showing me: not a platform, not a publishing habit, but repeated efforts to make information legible, navigable, impactful and connected, for the people reading it and increasingly for the machines reading on their behalf.&lt;/p&gt;
&lt;p&gt;The fresh coat of paint is fun. The datasheet vocabulary, the timeline and the Story Lab are there to make this collection legible, not just recent.&lt;/p&gt;
&lt;p&gt;Twenty years ago I turned two hundred words the state would not add up into a column of stars a parent could count at a glance. This is the same move, pointed at myself: taking a career that only ever arrived one post at a time and arranging it so the shape is finally countable. That is why the archive is worth keeping: enough context to see the shape, and enough structure to keep following it.&lt;/p&gt;
&lt;p&gt;I look forward to refining this, and hopefully that refines me.&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>Field notes on Mac OS 9.2, New World ROMs, and Apple Silicon</title>
    <link href="https://www.allaboutken.com/posts/20260626-down-the-stack/"/>
    <published>2026-06-26T00:00:00.000Z</published>
    <updated>2026-06-26T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260626-down-the-stack/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="retro computing" />
    <category term="career reflection" />
    <summary type="html">SheepShaver boots Mac OS 9 with a native JIT. Getting it on Apple Silicon? That took work. Full native 9.2? Even harder.</summary>
    <content type="html">
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/SheepShaver&quot;&gt;SheepShaver&lt;/a&gt; is a legendary Mac OS emulator (fast, compatible, beloved), but it cannot boot Mac OS 9.2. Iirc, you can coax it to run 9.2 through &lt;a href=&quot;https://web.archive.org/web/20160311205107/http://www.xlr8yourmac.com/OS9/os_9_2_1_install_mods.html&quot;&gt;hacks&lt;/a&gt;, but you still won&#39;t get all the features.&lt;/p&gt;
&lt;p&gt;I really wanted SheepShaver + 9.2 back in the early 2000s.&lt;/p&gt;
&lt;p&gt;Twenty-five years on, it is still a nope. You can use QEMU (on Apple Silicon, &lt;a href=&quot;https://mac.getutm.app/&quot;&gt;UTM&lt;/a&gt; is the best practical option for 9.2.2 today), but that is not SheepShaver, and you can feel it when using it.&lt;/p&gt;
&lt;p&gt;What I still want is SheepShaver&#39;s particular trick running classic Mac OS: a thin emulation layer with a &lt;abbr title=&quot;Just-In-Time compilation&quot;&gt;JIT&lt;/abbr&gt;. The JIT was only part of why it is fast, translating PowerPC instructions to native at runtime, but the bigger idea SheepShaver offered was what it declines to emulate. Rather than simulating a whole Macintosh, SheepShaver emulates the PowerPC CPU and patches the Mac &lt;abbr title=&quot;Read-Only Memory&quot;&gt;ROM&lt;/abbr&gt; to route everything else (I/O, memory, threads) straight to the host.&lt;/p&gt;
&lt;p&gt;Guest memory is just a region of the host&#39;s address space at a fixed offset, so a memory access is pointer arithmetic rather than a simulated lookup. It is not quite Wine on Linux, which translates an OS&#39;s API calls without running its kernel at all, but it sits much closer to that tightly integrated end of the spectrum than to a fully virtualized machine.&lt;/p&gt;
&lt;p&gt;QEMU JIT-translates PowerPC too: its &lt;a href=&quot;https://www.qemu.org/docs/master/devel/tcg.html&quot;&gt;TCG engine&lt;/a&gt; dynamically recompiles guest code to native ARM64. So the gap is not really JIT versus interpreter. It is everything around the CPU. QEMU simulates every piece of hardware the original Mac would have seen (the &lt;abbr title=&quot;Versatile Interface Adapter&quot;&gt;VIA&lt;/abbr&gt; chips that handle timers and interrupts, the &lt;abbr title=&quot;Apple Desktop Bus&quot;&gt;ADB&lt;/abbr&gt; controller behind the keyboard and mouse, the &lt;abbr title=&quot;Small Computer System Interface&quot;&gt;SCSI&lt;/abbr&gt; bus that talks to disks, the whole stack) rather than patching the OS to use host services directly. That means translating every guest memory address in software (a &lt;abbr title=&quot;Translation Lookaside Buffer&quot;&gt;TLB&lt;/abbr&gt; lookup) and calling out to C helper routines for low-level system instructions. Community benchmarks put SheepShaver at roughly 5× QEMU&#39;s overall score on comparable hardware, with the floating-point gap closer to 10×.&lt;/p&gt;
&lt;aside class=&quot;kh-marginnote&quot;&gt;&lt;a href=&quot;https://www.emaculation.com/forum/viewtopic.php?f=34&amp;t=8998&quot;&gt;E-Maculation MacBench 5.0 benchmarks&lt;/a&gt;: SheepShaver Floating Point 1955 vs QEMU 204; overall SpeedRun score 1283 vs 263. Tested on x86 hardware; Apple Silicon results follow the same pattern.&lt;/aside&gt;
&lt;p&gt;In practice QEMU/UTM on classic Mac workloads feels like running on a ~300 MHz G3: usable and stable, but noticeably slower than SheepShaver at 9.0.4. SheepShaver avoids most of that overhead because it was never a full-system emulator: it runs guest threads as real host threads and uses a JIT that skips supervisor-mode emulation entirely. That architecture is also why it tops out at 9.0.4: the New World ROM that 9.2 requires actually needs the hardware emulation layer SheepShaver has always refused to implement.&lt;/p&gt;
&lt;p&gt;Getting SheepShaver&#39;s speed and Mac OS 9.2 together, without hacks, turned out to be a much harder problem than it looked.&lt;/p&gt;
&lt;p&gt;The rabbit hole went deep.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;https://www.allaboutken.com/posts/20200208-its-been-20-years.html&quot;&gt;history with classic Mac OS&lt;/a&gt; was in desktop publishing: Quark 4 on Mac OS 7, PostScript ads that could lock the whole system, the peculiar texture of cooperative multitasking. By the time I was doing web work seriously, I was already on Mac OS X. What I had never touched was the programming side of it: what classic Mac development actually felt like, the toolchain, building something for the platform from scratch. I had always been curious about that.&lt;/p&gt;
&lt;p&gt;I have a Quicksilver G4 and a few iBooks in the basement that could theoretically address this. But they are loud, take up space, and draw significant power. So I have been approaching it from modern conveniences instead.&lt;/p&gt;
&lt;p&gt;Over the past year that has meant several experiments at once. &lt;a href=&quot;https://github.com/khawkins98/classic-vibe-mac&quot;&gt;Classic-vibe-mac&lt;/a&gt; compiles C source to a 68k Mac binary and runs it live in System 7.5.5 via WebAssembly Basilisk II; writing Mac programs without leaving the browser. &lt;a href=&quot;https://github.com/khawkins98/scriptoscope&quot;&gt;Scriptoscope&lt;/a&gt; is a Mac OS-style window manager and Kaleidoscope theme engine for the browser. &lt;a href=&quot;https://www.allaboutken.com/posts/20260216-90s-desktop-paradigm-browser-utilities/&quot;&gt;PDF-A-go-slim&lt;/a&gt; borrowed the floating palette paradigm from the 1995 Mac OS Human Interface Guidelines and applied it to a modern browser utility. All of that was aesthetics and runtime at arm&#39;s length from the actual software.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mplsllc/macsurf&quot;&gt;Macsurf&lt;/a&gt; pushed it closer — a classic Mac app I wanted to compile on QEMU. But that was slow, which led me back to SheepShaver, which led to the problem above.&lt;/p&gt;
&lt;aside class=&quot;kh-note-box&quot;&gt;
  &lt;span class=&quot;kh-note-box__label&quot;&gt;Practical verdict&lt;/span&gt;
  If you want to run the majority of classic Mac software, SheepShaver at 9.0.4 is faster. If you specifically need 9.2.x (for Final Cut Pro 3, late-era OpenGL games, or completeness), UTM/QEMU is the only real route today. Slower, but stable and perfectly functional for everyday use.
&lt;/aside&gt;
&lt;h2 id=&quot;the-detour-to-apple-silicon-and-then-92&quot; tabindex=&quot;-1&quot;&gt;The detour to Apple Silicon, and then 9.2&lt;a href=&quot;#the-detour-to-apple-silicon-and-then-92&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SheepShaver for ARM carries a fork chain:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the original &lt;a href=&quot;https://github.com/cebix/macemu&quot;&gt;cebix/macemu&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;community-maintained by &lt;a href=&quot;https://github.com/kanjitalk755/macemu&quot;&gt;kanjitalk755&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;then an ARM64 JIT backend by &lt;a href=&quot;https://github.com/rcarmo/macemu-jit&quot;&gt;rcarmo&lt;/a&gt; targeting Linux ARM boards.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nobody had yet taken it to macOS Apple Silicon. Getting there first meant porting that JIT backend to a platform with tighter constraints: MAP_JIT for executable pages, Mach exception handling instead of POSIX signals, address space limits that interact with SheepShaver&#39;s unusual architecture in specific ways.&lt;/p&gt;
&lt;p&gt;SheepShaver was designed in the late 1990s to run native PowerPC Mac code on native PowerPC Linux. There was originally nothing to emulate at all, just the Mac-specific hardware. That cooperative design is still baked in everywhere, and a lot of its assumptions about address space and memory layout needed rethinking for a modern macOS host.&lt;/p&gt;
&lt;p&gt;Getting to 9.2 is a different layer of the problem and one that isn&#39;t done yet for any flavor of SheepShaver.&lt;/p&gt;
&lt;p&gt;I&#39;ve put a lot of time into understanding how the nanokernel interacts with the &lt;abbr title=&quot;Memory Management Unit&quot;&gt;MMU&lt;/abbr&gt;, figuring out what needs to be properly modelled versus what can be safely sidestepped, and then circling back to do real implementations after those investigations proved the shortcuts would not hold. I had expected to draw more directly on existing work, and &lt;a href=&quot;https://github.com/dingusdev/dingusppc&quot;&gt;dingusppc&lt;/a&gt; in particular taught me a great deal. It takes the opposite approach to SheepShaver: full-system, device-accurate emulation of older PowerPC Macs (the &lt;abbr title=&quot;Serial Communications Controller&quot;&gt;SCC&lt;/abbr&gt; serial, VIA timer, and ADB controllers SheepShaver skips entirely), with a focus on Old World and beige-G3 hardware. That accuracy-first model is much closer to what the New World ROM actually expects, so it was a valuable reference for the hardware layer even though I was not trying to build the same kind of emulator. QEMU was the other early reference, but it turned out to be most useful as a diagnostic tool rather than an architectural model. The biggest unexpected source of inspiration was &lt;a href=&quot;https://dolphin-emu.org/&quot;&gt;Dolphin&lt;/a&gt;, the GameCube and Wii emulator: a completely different platform, but the same class of problem.&lt;/p&gt;
&lt;p&gt;Much of this has been done in collaboration with an AI coding partner. I wrote about that working pattern in &lt;a href=&quot;https://www.allaboutken.com/posts/20260420-let-ai-worry-about-the-code/&quot;&gt;let the AI worry about the code, you worry about the work&lt;/a&gt;. This project pushed it further than I expected: less about delegating implementation tasks, more about managing a technical investigation across sessions: knowing when to go deep, when to step back, when a hypothesis had been falsified and it was time to try something else. Project management skills translate into this kind of work more directly than I would have guessed.&lt;/p&gt;
&lt;p&gt;The result: SheepShaver boots Mac OS up to 9.0 to the Finder desktop on M-series Macs, with a native AArch64 JIT on by default. There is also preliminary AltiVec support, opt-in for now, because Mac OS 8.6 and 9.0 do not save vector registers across task switches.&lt;/p&gt;
&lt;p&gt;I&#39;m still not yet there for 9.2 support, but I am getting incrementally closer. Keep reading...&lt;/p&gt;
&lt;h2 id=&quot;what-is-next&quot; tabindex=&quot;-1&quot;&gt;What is next&lt;a href=&quot;#what-is-next&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Apple Silicon JIT work was the prerequisite. Mac OS 9.2 is the actual goal. It is worth being honest about why 9.2 specifically: the features it adds over 9.0 (preemptive multitasking, proper memory protection, 128-bit file encryption) are not things I actually need. Nobody is running production workloads on a classic Mac. But most ambitious hobby projects do not have a compelling real-world justification; that is more or less what makes them hobby projects. The point is getting the thing right. That said, if this all comes together, &lt;a href=&quot;https://support.apple.com/en-us/112424&quot;&gt;Final Cut Pro 3&lt;/a&gt; on SheepShaver becomes possible. Which is entirely unnecessary and, I think, quite cool.&lt;/p&gt;
&lt;p&gt;Running alongside it: SiliconSheep, a native macOS launcher for SheepShaver that makes the day-to-day experience less of a configuration exercise.&lt;/p&gt;
&lt;p&gt;Neither is done. This post is less an announcement than a status note: here is what has been happening, here is what is in progress.&lt;/p&gt;
&lt;h2 id=&quot;more-than-nostalgia&quot; tabindex=&quot;-1&quot;&gt;More than nostalgia&lt;a href=&quot;#more-than-nostalgia&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While a lot of this is nostalgia, there is a less sentimental case, and it comes down to how software gets written now.&lt;/p&gt;
&lt;p&gt;Building for classic Mac OS was always a grind: period hardware, a finicky toolchain, slow edit-build-test loops on machines that are rare and getting rarer. That friction is most of why the development scene faded. Coding agents change those economics. The same kind of AI partner that helped port this emulator can write 68k and PowerPC code and find its way around an unfamiliar toolchain, which collapses the loop that used to make this work tedious.&lt;/p&gt;
&lt;p&gt;Put a genuinely easy-to-run environment on modern hardware next to that, and a path opens up: writing new classic Mac software again. Some of it would run inside Mac OS 9 on an M-series laptop; some could compile down to the real beige hardware still humming in basements. &lt;a href=&quot;https://github.com/khawkins98/classic-vibe-mac&quot;&gt;Classic-vibe-mac&lt;/a&gt; was a first hint of this; a fast SheepShaver is the other half. I am not promising a renaissance. But the thing that always killed it was friction, and removing friction is exactly what these tools are good at. Get the development experience right and the rest becomes possible.&lt;/p&gt;
&lt;p&gt;I do not have formal domain expertise in emulator development. What I have is curiosity, some stubbornness, and an AI partner that turned out to be a good collaborator on gnarly low-level problems. AI will not be to everyone&#39;s taste, and I understand that. It is a hobby project; I am sharing it back in case any of it is useful, not because anyone has to approve of the method.&lt;/p&gt;
&lt;p&gt;How far this goes, I am not sure yet. But the Finder boots on my M-series Mac with JIT, and I think I could crack 9.2, too.&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>CADI: a prescriptive path for slop-free AI in content work</title>
    <link href="https://www.allaboutken.com/posts/20260609-iterative-pattern-framework/"/>
    <published>2026-06-09T00:00:00.000Z</published>
    <updated>2026-06-09T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260609-iterative-pattern-framework/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="AI" />
    <category term="editorial workflow" />
    <category term="content strategy" />
    <summary type="html">A framework to collect, analyze, document, iterate (CADI, for short) is a prescriptive answer to the question everyone keeps asking: should I prompt better, write a style guide, build an agent, or what?</summary>
    <content type="html">
&lt;p&gt;Recently I was in a meeting with colleagues talking about how people across the organisation are using AI in their writing. What I heard:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;I just prompt it and ship. We don&#39;t have time to fuss.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;I use it for ideas, but I still write the piece myself.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;I rewrite every line. I&#39;m honestly not sure I&#39;m saving any time.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;It&#39;s infuriating.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;I see other people using it and I see the same repetitive AI tells bleeding through.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;I&#39;m not convinced any of this is worth it. The old way was quieter and probably better.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Other organisations are using it. We can&#39;t be the only ones not.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;—&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Should I just get better at prompting?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Should I write a style guide?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Should I build an agent?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Should I document my preferences somewhere?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Should I upload my existing content?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The honest answer is always some version of &amp;quot;yes, but in a specific order, and most of the value comes from the combination.&amp;quot; Helping colleagues do better is frustrating as it&#39;s a repetitive conversation and it&#39;s far too conceptual for many to put in practice.&lt;/p&gt;
&lt;p&gt;So I documented it. This is &lt;strong&gt;CADI&lt;/strong&gt; — Collect, Analyze, Document, Iterate — a prescriptive path through the confusion.&lt;/p&gt;
&lt;p&gt;The name nods to a caddie in golf: carries the load, doesn&#39;t take the swing. It&#39;s designed to be followed roughly in order, at least the first time through.&lt;/p&gt;
&lt;p&gt;An experienced editor with access to your content can produce a first usable version of the guidance in a few hours by feeding curated examples to a language model and shaping what comes back. Then it&#39;s an editorial process: share it with colleagues, use it for a few days, fold their reactions in. Each round sharpens the guidance; the work doesn&#39;t get heavier, the picture gets clearer. It works for any content type or language, as long as you have enough of it to learn from.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-problem-every-prompt-starts-from-zero&quot; tabindex=&quot;-1&quot;&gt;The problem: every prompt starts from zero&lt;a href=&quot;#the-problem-every-prompt-starts-from-zero&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most AI writing assistance starts cold every time. No memory of what worked last time. No sense of your voice, your standards, your audience. So it gives you something generic and confident, and you spend the rest of your time correcting it.&lt;/p&gt;
&lt;p&gt;The fix isn&#39;t a better prompt. It&#39;s giving the AI a documented picture of what good looks like &lt;em&gt;for you&lt;/em&gt;, and building a way to improve that picture as you learn what works. Done right, the AI gets more useful over time rather than requiring the same effort on every task.&lt;/p&gt;
&lt;p&gt;There&#39;s a failure mode that persists even after you think you&#39;ve solved this: &lt;strong&gt;confident wrongness + silent drift&lt;/strong&gt;. The AI sounds authoritative but cites a source incorrectly. It hits approximately the right word count but adds a paragraph that subtly changes your meaning. These are harder to catch than obvious nonsense, and they accumulate faster.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;where-you-probably-are-supervised-co-writing&quot; tabindex=&quot;-1&quot;&gt;Where you probably are: supervised co-writing&lt;a href=&quot;#where-you-probably-are-supervised-co-writing&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most people using AI for writing aren&#39;t doing &amp;quot;autonomous AI publishing.&amp;quot; They&#39;re doing &lt;strong&gt;supervised co-writing&lt;/strong&gt;: AI handles a first pass or a tedious task, a human reviews and corrects, a human publishes. That&#39;s normal; it&#39;s also where most of the real value is right now.&lt;/p&gt;
&lt;p&gt;CADI is for teams who want to move from &amp;quot;AI is something we sometimes use&amp;quot; to &amp;quot;AI is something our editorial standards reliably shape.&amp;quot; That transition doesn&#39;t happen through better prompting alone.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;before-you-start-five-things-to-check&quot; tabindex=&quot;-1&quot;&gt;Before you start: five things to check&lt;a href=&quot;#before-you-start-five-things-to-check&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This framework works best when you can answer yes to these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have &lt;strong&gt;ideally 50+ published items&lt;/strong&gt; you consider high-quality (not just recent). 10–20 from a tight, high-quality domain still works; you&#39;ll overfit a little, but the discipline of running the loop is what compounds&lt;/li&gt;
&lt;li&gt;You have some signal for quality: engagement data, editor picks, longevity tags, or even manual curation&lt;/li&gt;
&lt;li&gt;At least one person has &lt;strong&gt;time to own the guidance doc&lt;/strong&gt; and keep it current&lt;/li&gt;
&lt;li&gt;You have some way to &lt;strong&gt;export content&lt;/strong&gt; from your CMS in text form (Markdown, HTML, CSV)&lt;/li&gt;
&lt;li&gt;Editors are willing to &lt;strong&gt;log failures&lt;/strong&gt; rather than just silently fix them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don&#39;t need a data science team, a custom CMS, or a formal AI strategy. But if no one will own the guidance document, the method breaks down.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;pick-your-tool&quot; tabindex=&quot;-1&quot;&gt;Pick your tool&lt;a href=&quot;#pick-your-tool&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before you start, decide how you&#39;ll feed your content to an LLM. The choice shapes every step that follows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://notebooklm.google.com/&quot;&gt;NotebookLM&lt;/a&gt; or similar.&lt;/strong&gt; Best if your content is web-accessible. Drop URLs or PDFs as sources; run prompts in the chat. Zero file handling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A local agent (Claude Code, GitHub Copilot CLI, or similar).&lt;/strong&gt; Best if you&#39;ve exported your content as files. Point the agent at the directory and ask it to run the prompts. Handles iteration and cleanup for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual pipeline (browser-only).&lt;/strong&gt; Fallback when the other two aren&#39;t options. You&#39;ll preprocess HTML, concatenate, and paste into a browser tool. The &lt;a href=&quot;https://www.allaboutken.com/resources/cadi-starter-kit.txt&quot;&gt;starter kit&lt;/a&gt; has the prompts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rest of the post assumes you&#39;ve picked one. When a step says &amp;quot;run the Analyze prompt&amp;quot; or &amp;quot;feed the corpus to your model,&amp;quot; substitute your chosen tool&#39;s mechanics.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-cadi-framework-collect-analyze-document-iterate&quot; tabindex=&quot;-1&quot;&gt;The CADI framework: Collect → Analyze → Document → Iterate&lt;a href=&quot;#the-cadi-framework-collect-analyze-document-iterate&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CADI is a four-phase loop. You build it once, then run the last phase continuously. Each cycle makes the guidance sharper, and the AI more useful.&lt;/p&gt;
&lt;p&gt;The bit most AI-writing advice misses: the guidance document is something you test and revise. C, A, and D get you a first version. I is where it earns its keep.&lt;/p&gt;
&lt;p&gt;I&#39;ve arrived at this through practice, and written about the pieces separately. This is the first time I&#39;ve named it.&lt;/p&gt;
&lt;h3 id=&quot;c-collect-extract-your-best-examples&quot; tabindex=&quot;-1&quot;&gt;C — Collect: Extract your best examples&lt;a href=&quot;#c-collect-extract-your-best-examples&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Your best content already embodies your patterns: word counts, title structures, tag usage, narrative flow. Don&#39;t grab the latest 50 published items; &lt;strong&gt;curate the exemplary 50.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start with one content subtype, not a mix.&lt;/strong&gt; Most organisations publish several things that look similar but follow different patterns: press releases, news briefs, feature reports, donor updates, blog posts. Each has its own voice and structure. Mixing them gives you average patterns that fit none of them. Pick the subtype that matters most to you right now and run CADI on that. Repeat for other subtypes later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&#39;t default to top-traffic items.&lt;/strong&gt; That&#39;s the obvious choice and the most common mistake. High traffic often correlates with topic timeliness, social-share luck, or audience reach, not with the qualities you&#39;d want to reproduce. Three signals to combine instead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Editorial investment&lt;/strong&gt; — items your team spent the most time on. Effort is a quality signal even when the numbers aren&#39;t there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aspirational fit&lt;/strong&gt; — items you&#39;d want more of your output to look like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance &lt;em&gt;for type&lt;/em&gt;&lt;/strong&gt; — a press release that did 5× the median for press releases tells you more than a feature that hit Hacker News.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Document the criteria you used. It will matter when you audit later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bring your existing reference materials too.&lt;/strong&gt; Your best content shows the agent &lt;em&gt;what good looks like&lt;/em&gt; by example. Your existing editorial handbook, brand guidelines, glossary, terminology and spelling lists, institutional style memos — these tell it the &lt;em&gt;rules&lt;/em&gt; you want enforced. Add them as sources alongside the corpus. The agent should be able to &lt;em&gt;cite them&lt;/em&gt; when producing drafts (&amp;quot;per our spelling guide, we use &#39;organization&#39; with a z&amp;quot;) and &lt;em&gt;link to them&lt;/em&gt; when justifying its choices. If your handbook says &amp;quot;no Oxford comma,&amp;quot; the corpus pattern alone might not surface that as a hard rule.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Getting the files to your tool.&lt;/strong&gt; With NotebookLM, add the URLs or PDFs of your 50–100 best items as sources. With a local agent, export your content as HTML to a directory and point the agent there. With the manual pipeline, follow the four steps in the &lt;a href=&quot;https://www.allaboutken.com/resources/cadi-starter-kit.txt&quot;&gt;starter kit&lt;/a&gt;. One-time setup per content subtype.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Ready to move on when&lt;/strong&gt;: you have 50–100 items in a single consolidated, cleaned document; you&#39;ve written down why they qualify; and an editor has spot-checked for obvious outliers.&lt;/p&gt;
&lt;p&gt;For implementation detail, including Drupal export code and a worked UNDRR case study, see &lt;a href=&quot;https://www.allaboutken.com/posts/evidence-based-content-guidelines-drupal-ai/&quot;&gt;Improving AI chatbots with an editorial handbook from your best content&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;a-analyze-find-quantitative-patterns&quot; tabindex=&quot;-1&quot;&gt;A — Analyze: Find quantitative patterns&lt;a href=&quot;#a-analyze-find-quantitative-patterns&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Feed your curated examples to an LLM with a structured prompt to extract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average word counts (with ranges, by content type)&lt;/li&gt;
&lt;li&gt;Title formats and syntactic shapes (% using colons or question marks; whether titles assert claims, ask questions, restate the source headline, or use first-person observation)&lt;/li&gt;
&lt;li&gt;Heading and structural patterns (do longer pieces get H2s? At what word-count threshold? Lists vs. prose?)&lt;/li&gt;
&lt;li&gt;Voice markers (first vs. third person, contractions, hedging language, sentence-length variance)&lt;/li&gt;
&lt;li&gt;Source treatment (% linking sources inline, % using blockquotes from the source, whether the source is named in the first paragraph)&lt;/li&gt;
&lt;li&gt;Opening and closing patterns (does the first paragraph name a topic, a scene, a source? Does the last forward-link or name a broader pattern?)&lt;/li&gt;
&lt;li&gt;Cross-linking density (internal links per piece; what fraction connect to other content on the site)&lt;/li&gt;
&lt;li&gt;Metadata usage (tag counts, co-occurrence patterns)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This list isn&#39;t exhaustive.&lt;/strong&gt; Your subtype will have its own tells — repeated structural moves, signature phrasing, specific rhetorical positioning. Add the signals that matter for your content; document what you watched for. It&#39;ll be the first thing you revisit on the next cycle.&lt;/p&gt;
&lt;p&gt;LLMs handle this extraction well; the interpretation is still yours. The output is not guidance yet; it&#39;s &lt;strong&gt;empirical description&lt;/strong&gt; of what your best content actually looks like.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Ready to move on when&lt;/strong&gt;: you have a written summary of patterns with numbers, and at least one editor has reviewed it and flagged anything that looks wrong or unrepresentative.&lt;/p&gt;
&lt;h3 id=&quot;d-document-turn-patterns-into-working-instructions&quot; tabindex=&quot;-1&quot;&gt;D — Document: Turn patterns into working instructions&lt;a href=&quot;#d-document-turn-patterns-into-working-instructions&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Combine what the analysis found with your existing institutional knowledge (style guides, brand standards, domain constraints) into a single document your AI tool can use as its standing instructions. Link or embed any existing reference docs your team already maintains; the guidance doc should point at them rather than restate them, so editors and the agent can both trace any rule back to its source.&lt;/p&gt;
&lt;p&gt;If your existing style guide already addresses this subtype, the Document step isn&#39;t to re-derive from scratch. It&#39;s to tighten the existing guidance with the numbers the Analyze step produced, and override only where you have evidence the guidance was wrong.&lt;/p&gt;
&lt;p&gt;This step requires editorial judgment: observed patterns and existing style rules will sometimes conflict. If the analysis says your best titles average 8 words but house style caps them at 6, decide which wins and log why. Write it down; that decision belongs in your change log, not left implicit.&lt;/p&gt;
&lt;p&gt;You end up with instructions concrete enough to act on (&amp;quot;180 words ± 10%&amp;quot;) rather than vague (&amp;quot;be concise&amp;quot;). Bonus: the same document works for human editors too.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Ready to move on when&lt;/strong&gt;: the instructions have been reviewed by at least one editor who wasn&#39;t involved in writing them, and they&#39;ve been uploaded or integrated with your AI tool.&lt;/p&gt;
&lt;h3 id=&quot;i-iterate-build-confidence-over-time&quot; tabindex=&quot;-1&quot;&gt;I — Iterate: Build confidence over time&lt;a href=&quot;#i-iterate-build-confidence-over-time&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Iteration done badly is just tweaking in the dark, which is how we started. Done well, it&#39;s hypothesis-driven:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;IF we add explicit numeric targets (&#39;180 words ± 10%&#39;), THEN first-pass acceptance rises from 40% to 70%&amp;quot; &lt;em&gt;(illustrative)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&amp;quot;IF we show exemplars of title styles, THEN AI chooses appropriate titles 85% of the time&amp;quot; &lt;em&gt;(illustrative)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&amp;quot;IF we run oppositional review (a second model critiques our guidance), THEN we catch biased rules 80% of the time&amp;quot; &lt;em&gt;(illustrative)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then test them: run a simple before/after comparison on real content, measure outcomes with a concrete metric, and document what changed and why.&lt;/p&gt;
&lt;p&gt;For a small team (2–3 editors), 20–30 items per condition is enough for an honest qualitative comparison: better than tweaking blind, not a statistical signal. Anything closer to statistical rigour needs much larger samples and blinded review; most editorial teams won&#39;t reach that, and that&#39;s fine. The bar is qualitative honesty: what changed, and why.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mid-cycle revision trigger&lt;/strong&gt;: if more than 20% of outputs in a week fail the same check, don&#39;t wait for the quarterly audit. That&#39;s a pattern worth investigating now.&lt;/p&gt;
&lt;p&gt;None of this is invented from scratch. Other disciplines have been at versions of this for decades:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.writer.com/article/250-how-to-calibrate-voice-for-your-content&quot;&gt;Writer&lt;/a&gt; and &lt;a href=&quot;https://help.jasper.ai/hc/en-us/articles/18618693085339-Brand-Voice&quot;&gt;Jasper&lt;/a&gt; will both reverse-engineer a voice profile from examples of your own content; worth knowing about, and worth using if they fit your stack.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2309.09128&quot;&gt;ChainForge&lt;/a&gt; (CHI 2024) formalized treating prompts, and the evaluation criteria themselves, as testable hypotheses.&lt;/li&gt;
&lt;li&gt;Medical guideline development (AGREE II and GRADE) assess whether guidelines are evidence-based and implementable. The &lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC7657075/&quot;&gt;Australian COVID-19 living guidelines&lt;/a&gt; went further: revised and republished weekly for 24 weeks as new evidence arrived, a continuously iterated guidance document running in production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But a software subscription isn&#39;t a method, and a research paper isn&#39;t a guide. What I haven&#39;t seen elsewhere is the connected discipline: deriving guidance from your own curated corpus, then keeping the failure log and test record that make that guidance revisable on evidence rather than vibes. That combination is what CADI names. If reading this sends you off to subscribe to one of those tools or build your own loop from the research, good; that&#39;s the point. Prior art still welcome.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Ready to move on when&lt;/strong&gt;: you have at least one completed hypothesis test, a documented decision (kept or rejected), and a failure log that someone is actually adding to.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cadi-in-practice-how-it-played-out-at-undrr&quot; tabindex=&quot;-1&quot;&gt;CADI in practice: how it played out at UNDRR&lt;a href=&quot;#cadi-in-practice-how-it-played-out-at-undrr&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One case illustrates the full loop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Collection&lt;/strong&gt;: 122 publications flagged as &amp;quot;evergreen + high-engagement&amp;quot; in Drupal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analysis&lt;/strong&gt;: LLM extracted patterns. &amp;quot;169 words average; 56% use colons in title; all use 2–4 themes&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Guidance doc drafted and uploaded as standing instructions in Microsoft 365 Copilot&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iteration&lt;/strong&gt;: Quarterly audits checked whether new AI drafts matched patterns; a shared change log recorded what was adjusted and why&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The honest version of the result: before CADI, none of these pieces (curation, pattern extraction, guidance docs, iteration) reliably produced anything usable on its own. They were ad-hoc, inconsistent, and each task started over. Running them as a connected loop turned a set of one-off attempts into a process that ran continuously and got better. The framework&#39;s value wasn&#39;t a single big number; it was making something repeatable that hadn&#39;t been. The longer case file (the original methodology, plus what the guidance doc looked like in practice) is in the &lt;a href=&quot;https://www.allaboutken.com/work/2025/impact-story-evidence-based-content-guidelines/&quot;&gt;UNDRR impact story&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And I&#39;m not pretending what I have is rigorous yet. What would make the next version &lt;em&gt;genuinely rigorous&lt;/em&gt; is a prospective A/B test that quantifies which guidance changes move which outcomes by how much. That&#39;s the piece most teams skip; writing this down, sharing it, and letting it be challenged is part of how I keep tightening the loop.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;using-cadi-in-the-writing-workflow&quot; tabindex=&quot;-1&quot;&gt;Using CADI in the writing workflow&lt;a href=&quot;#using-cadi-in-the-writing-workflow&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CADI gives you the guidance layer. The day-to-day writing that uses it has a shape of its own:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ideation&lt;/strong&gt; — what are we writing, for whom, why now? AI helps brainstorm; the editorial decision is yours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scaffolding&lt;/strong&gt; — outline, key points, source material. CADI&#39;s guidance doc steers tone and structure; the agent helps assemble.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drafting&lt;/strong&gt; — produce the first pass with the guidance doc as the agent&#39;s standing context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review&lt;/strong&gt; — apply role-based passes for the checks this piece needs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The review step is where you pick agent roles that match the piece&#39;s risk and shape. The ones I&#39;ve found useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Copy editor&lt;/strong&gt; — grammar, flow, voice consistency against the guidance doc&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adversarial reviewer&lt;/strong&gt; — does the argument hold? What&#39;s the strongest counter?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legal / compliance check&lt;/strong&gt; — for content with regulatory exposure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copyright check&lt;/strong&gt; — for content with quotation, imagery, or external sourcing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical reviewer&lt;/strong&gt; — for content making claims about systems, code, or data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sanity check&lt;/strong&gt; — final read for &amp;quot;does this make sense, end to end?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not every piece needs every role; pick what fits the content type and its risk profile. The CADI guidance doc gets stronger when failure-log entries name &lt;em&gt;which role&lt;/em&gt; would have caught the failure — that&#39;s how you learn which checks belong in which subtype&#39;s workflow.&lt;/p&gt;
&lt;p&gt;A deeper walk-through, with prompts and examples for each role, is coming in a follow-up post.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;trust-but-verify-the-publication-gate&quot; tabindex=&quot;-1&quot;&gt;Trust but verify: the publication gate&lt;a href=&quot;#trust-but-verify-the-publication-gate&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The trust-but-verify gate sits between any CADI-assisted draft and publication. Don&#39;t skip it, even once you trust the system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Verify factual claims and sources independently&lt;/li&gt;
&lt;li&gt;Verify hard constraints (word/character counts, required fields). Don&#39;t trust the model&#39;s own count&lt;/li&gt;
&lt;li&gt;Verify meaning hasn&#39;t drifted from source intent&lt;/li&gt;
&lt;li&gt;Keep human sign-off as the final release gate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For many teams, a &lt;strong&gt;parallel run&lt;/strong&gt; is the safest transition: keep your normal manual editorial review while running AI-assisted output alongside it. Compare decisions, measure drift, then scale what proves useful.&lt;/p&gt;
&lt;p&gt;This is also the spirit of the editorial AI policies emerging in newsrooms, where &lt;a href=&quot;https://www.allaboutken.com/posts/20260423-digesting-ars-technica-ai-newsroom-policy/&quot;&gt;Ars Technica and Fedora make &amp;quot;reviewed&amp;quot; a protected verb&lt;/a&gt; with the human review step explicit and named. CADI takes the same position: the loop doesn&#39;t replace the gate; it makes the gate cheaper to operate.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;where-this-breaks-down&quot; tabindex=&quot;-1&quot;&gt;Where this breaks down&lt;a href=&quot;#where-this-breaks-down&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Iteration goes blind again when:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Encoding bias without inspection&lt;/strong&gt;: a pattern like &amp;quot;short sentences good&amp;quot; might just reflect that your exemplars targeted beginners; applying it universally infantilizes advanced content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimizing for fit, not truth&lt;/strong&gt;: &amp;quot;this tag co-occurs 26% of the time, so ALWAYS pair them&amp;quot; (should be &amp;quot;consider pairing&amp;quot;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No quality gate on source&lt;/strong&gt;: including mediocre content in your &amp;quot;best&amp;quot; set skews guidance toward mediocrity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stopping too early&lt;/strong&gt;: 10 examples isn&#39;t enough; 500 might over-fit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expecting voice cloning in informal genres&lt;/strong&gt;: the style-imitation research is encouraging for structured formats like news and email, and &lt;a href=&quot;https://arxiv.org/abs/2509.14543&quot;&gt;much weaker for informal, personal writing&lt;/a&gt; like blogs and forum posts. Corpus-derived guidance reliably reproduces structure and conventions; a distinctive personal voice still needs the human pass&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Prevention: transparent curation criteria, documented assumptions, human review between cycles, oppositional analysis (ask a second model to critique your instructions), and built-in escape hatches (&amp;quot;usually X, unless Y&amp;quot;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where CADI doesn&#39;t fit cleanly:&lt;/strong&gt; high-volume newsrooms shipping dozens of pieces per week across many content subtypes. CADI assumes one primary subtype at a time and a corpus you can read in full. Sampling methodology and per-subtype parallel runs are needed there. Out of scope for this post, but the same loop applies once you&#39;ve made those decisions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-you-maintain-alongside-the-loop&quot; tabindex=&quot;-1&quot;&gt;What you maintain alongside the loop&lt;a href=&quot;#what-you-maintain-alongside-the-loop&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These four documents are what CADI actually leaves you with. The starter kit has templates for each: &lt;a href=&quot;https://www.allaboutken.com/resources/cadi-starter-kit.txt&quot;&gt;cadi-starter-kit.txt&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A curation criteria note&lt;/strong&gt; — why these items qualified as exemplary, and not others&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your working AI instructions&lt;/strong&gt; — the guidance doc the AI uses as its standing context&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A failure log&lt;/strong&gt; — the one document that&#39;s more valuable than the guidance doc itself; it&#39;s where the next iteration starts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A test record&lt;/strong&gt; — what you tested, what changed, and why&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If no one is keeping at least the failure log current, you&#39;re not iterating; you&#39;re just running C/A/D once and hoping.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;after-your-first-cycle-go-deeper&quot; tabindex=&quot;-1&quot;&gt;After your first cycle, go deeper&lt;a href=&quot;#after-your-first-cycle-go-deeper&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These earlier posts go deeper on individual phases. You don&#39;t need to read them to start.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/evidence-based-content-guidelines-drupal-ai/&quot;&gt;Improving AI chatbots with an editorial handbook from your best content&lt;/a&gt;&lt;/strong&gt;: the C and A phases in detail, with Drupal export code and the full UNDRR case study (updated June 2026)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20250923-effective-ai-work-is-project-management/&quot;&gt;The power of the pause: How planning beats prompt tuning&lt;/a&gt;&lt;/strong&gt;: structure over tweaking, with PROJECT_BRIEF → PROJECT_PLAN → PATTERNS workflow&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20250919-smart-ai-integration-context-first/&quot;&gt;Thoughtful AI integration beats bolted-on Clippy&lt;/a&gt;&lt;/strong&gt;: product thinking for AI integration, when to use it, not just how to prompt it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260330-context-was-always-the-job/&quot;&gt;Why context engineering was always the job&lt;/a&gt;&lt;/strong&gt;: where CADI sits in the broader move toward context as the work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260405-four-phases-of-ai-adoption/&quot;&gt;The four phases of AI adoption&lt;/a&gt;&lt;/strong&gt;: the Complement / Integrate / Delegate / Orchestrate taxonomy; CADI lives around Integrate and Delegate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CADI is the loop we run at UNDRR. Think of it as a forcing function more than a magic generator: most of the work is still you reading your own content carefully. The framework just makes sure you do it, and write down what you learn.&lt;/p&gt;
&lt;p&gt;It works for us; it might work for you; or it might need reshaping for the volume, content type, or constraints you&#39;re in. I&#39;d be curious how it lands once you try it: what you change, what breaks, what surprises.&lt;/p&gt;
&lt;p&gt;One direction I&#39;m exploring: packaging CADI as a guided Claude skill (or similar) that asks where your content lives, walks you through the curation criteria, runs the Analyze prompt for you, and hands you a draft guidance document ready for editorial review. If that&#39;d be useful, drop me a note.&lt;/p&gt;
&lt;p&gt;The first run of this framework on this site is preserved as a worked artifact: &lt;a href=&quot;https://www.allaboutken.com/resources/cadi-cycles/2026-06-digesting/&quot;&gt;CADI cycle 1 — digesting posts (June 2026)&lt;/a&gt;. Future cycles will build on it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References&lt;a href=&quot;#references&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bsharat et al., &amp;quot;&lt;a href=&quot;https://arxiv.org/abs/2312.16171&quot;&gt;Principled Instructions Are All You Need&lt;/a&gt;&amp;quot; (arXiv:2312.16171): a preprint reporting an average 57.7% quality boost from 26 generic prompting principles on GPT-4. Small, author-judged benchmark; read it as directional support for structured prompts, not as evidence for corpus-derived guidance specifically&lt;/li&gt;
&lt;li&gt;&amp;quot;&lt;a href=&quot;https://arxiv.org/abs/2509.14543&quot;&gt;Catch Me If You Can? Not Yet: LLMs Still Struggle to Imitate the Implicit Writing Styles of Everyday Authors&lt;/a&gt;&amp;quot; (EMNLP 2025 Findings): exemplar-based prompting consistently beats instruction-only prompting for style matching; strong in structured genres, weak in informal ones&lt;/li&gt;
&lt;li&gt;Wu et al., &amp;quot;&lt;a href=&quot;https://arxiv.org/abs/2302.07346&quot;&gt;ScatterShot: Interactive In-context Example Curation for Text Transformation&lt;/a&gt;&amp;quot; (ACM IUI 2023): systematic example curation beats ad-hoc hand-picking, which tends to capture only the most obvious patterns&lt;/li&gt;
&lt;li&gt;Arawjo et al., &amp;quot;&lt;a href=&quot;https://arxiv.org/abs/2309.09128&quot;&gt;ChainForge: A Visual Toolkit for Prompt Engineering and LLM Hypothesis Testing&lt;/a&gt;&amp;quot; (CHI 2024)&lt;/li&gt;
&lt;li&gt;Tendal et al., &amp;quot;&lt;a href=&quot;https://pmc.ncbi.nlm.nih.gov/articles/PMC7657075/&quot;&gt;Weekly updates of national living evidence-based guidelines: methods for the Australian living guidelines for care of people with COVID-19&lt;/a&gt;&amp;quot; (J Clin Epidemiol 2020)&lt;/li&gt;
&lt;li&gt;Wei et al., &amp;quot;&lt;a href=&quot;https://arxiv.org/abs/2201.11903&quot;&gt;Chain-of-Thought Prompting Elicits Reasoning&lt;/a&gt;&amp;quot; (2022)&lt;/li&gt;
&lt;li&gt;Yao et al., &amp;quot;&lt;a href=&quot;https://arxiv.org/abs/2305.10601&quot;&gt;Tree of Thoughts: Deliberate Problem Solving with LLMs&lt;/a&gt;&amp;quot; (2023)&lt;/li&gt;
&lt;li&gt;Kohavi, Longbotham et al., &amp;quot;&lt;a href=&quot;https://doi.org/10.1007/s10618-008-0114-1&quot;&gt;Controlled experiments on the web: survey and practical guide&lt;/a&gt;&amp;quot; (2009, &lt;em&gt;Data Mining and Knowledge Discovery&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.agreetrust.org/&quot;&gt;AGREE II&lt;/a&gt; — instrument for appraising the quality of clinical practice guidelines (the discipline of judging whether your guidance itself is sound)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gradeworkinggroup.org/&quot;&gt;GRADE&lt;/a&gt; — framework for grading the certainty of evidence and the strength of recommendations (separating &amp;quot;we&#39;re sure&amp;quot; from &amp;quot;we think&amp;quot;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://diataxis.fr/&quot;&gt;Diátaxis&lt;/a&gt; — documentation framework organising knowledge by user need (tutorials, how-tos, reference, explanation) rather than by topic&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.prisma-statement.org/&quot;&gt;PRISMA&lt;/a&gt; — reporting standard for systematic reviews (a checklist for how to describe what you did, so others can trust or replicate it)&lt;/li&gt;
&lt;/ul&gt;

</content>
  </entry>
  <entry>
    <title>A data font, from the inside out</title>
    <link href="https://www.allaboutken.com/posts/20260512-datatype-data-font-inside-out/"/>
    <published>2026-05-12T00:00:00.000Z</published>
    <updated>2026-05-12T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260512-datatype-data-font-inside-out/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="typography" />
    <category term="data visualization" />
    <category term="design systems" />
    <summary type="html">Datatype is a variable font that turns text into tiny inline charts.</summary>
    <content type="html">


&lt;p&gt;&lt;a href=&quot;https://franktisellano.github.io/datatype/&quot;&gt;Datatype&lt;/a&gt;, by Frank Tisellano, is a variable font that renders inline bar charts, sparklines, and pie charts straight out of OpenType ligatures. Write &lt;code&gt;{b:30,70,50,90}&lt;/code&gt; and get &lt;span class=&quot;chart&quot; aria-hidden=&quot;true&quot;&gt;{b:30,70,50,90}&lt;/span&gt;; no JavaScript, no SVG, no charting library involved. It&#39;s also a total inversion of a 2018 piece of mine, &lt;a href=&quot;https://www.allaboutken.com/posts/20180319-data-font-for-life-sciences.html&quot;&gt;A web font for data&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Datatype turns text like &lt;code&gt;{b:30,70,50,90}&lt;/code&gt; into inline charts via OpenType ligatures: bar, sparkline, and pie, with weight and width axes.&lt;/li&gt;
&lt;li&gt;It rhymes with a 2018 piece of mine, &lt;a href=&quot;https://www.allaboutken.com/posts/20180319-data-font-for-life-sciences.html&quot;&gt;What if: A web font for data&lt;/a&gt;, but inverts it: my version was a font for &lt;em&gt;reading&lt;/em&gt; dense data, this is a font that &lt;em&gt;is&lt;/em&gt; the data.&lt;/li&gt;
&lt;li&gt;The upstream project ships zero accessibility guidance; for any serious use, pair each chart with &lt;code&gt;aria-hidden&lt;/code&gt; on the glyph span and a visually-hidden data table. There&#39;s a copyable pattern below.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;a-data-font-and-font-for-data&quot; tabindex=&quot;-1&quot;&gt;A data font and font for data&lt;a href=&quot;#a-data-font-and-font-for-data&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Back in 2018, while at EMBL-EBI, I wrote a long piece called &lt;a href=&quot;https://www.allaboutken.com/posts/20180319-data-font-for-life-sciences.html&quot;&gt;What if: A web font for data&lt;/a&gt;. My problem was that life-sciences researchers stare at strings like &lt;code&gt;ENSMUST00000032717&lt;/code&gt; all day, and the tools they were using rendered those strings in fonts that had not been designed with that kind of work in mind. Sequential zeros were ambiguous (is &lt;code&gt;20000001&lt;/code&gt; six zeros or seven?). Lowercase &lt;code&gt;l&lt;/code&gt;, uppercase &lt;code&gt;I&lt;/code&gt;, and numeric &lt;code&gt;1&lt;/code&gt; could be hard to tell apart in body widths. &lt;code&gt;BuMpy caPitiLiSation&lt;/code&gt; patterns were brittle to read at a glance. My sketch was a font where the ligature feature did the same kind of work &lt;a href=&quot;https://github.com/tonsky/FiraCode&quot;&gt;Fira Code&lt;/a&gt; does for code, but pointed at scientific identifiers: chunking long zero runs, disambiguating glyphs, giving the &lt;em&gt;reader&#39;s eye&lt;/em&gt; better handles.&lt;/p&gt;
&lt;p&gt;Datatype is also a data font, and it&#39;s also built on the OpenType ligature feature. But it inverts every part of the goal. The text isn&#39;t there to be read in the normal sense; it&#39;s there to be drawn. The ligature substitution doesn&#39;t help you decode the characters — it replaces them entirely with a chart. The font isn&#39;t a better tool for the reader. The font &lt;em&gt;is&lt;/em&gt; the data.&lt;/p&gt;
&lt;p&gt;Clearly the aims are different, but the mirror universe aspect tickles my brain.&lt;/p&gt;
&lt;p&gt;Datatype also credits IBM Plex Mono as the source of some underlying glyphs, and IBM Plex was the typeface I &lt;a href=&quot;https://www.allaboutken.com/posts/20171112-ibm-plex-font-and-fira.html&quot;&gt;wrote about back in 2017&lt;/a&gt; when we were evaluating it for EMBL-EBI&#39;s scientific data work. Neat.&lt;/p&gt;
&lt;h2 id=&quot;accessibility-gap&quot; tabindex=&quot;-1&quot;&gt;Accessibility gap&lt;a href=&quot;#accessibility-gap&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Data viz accessibility is always a challenge. Left alone, a string like &lt;code&gt;{b:18,21,12,12,15,0,0,0,100,94}&lt;/code&gt; gets read aloud verbatim, brace by digit by comma. That&#39;s worse than no alternative at all.&lt;/p&gt;
&lt;p&gt;This is made a bit better by the &lt;a href=&quot;https://www.w3.org/WAI/tutorials/images/complex/&quot;&gt;WAI pattern for complex graphics&lt;/a&gt;. Mark the glyph span &lt;code&gt;aria-hidden&lt;/code&gt;, and pair each chart with a visually-hidden data table that assistive technology can navigate as proper rows and cells:&lt;/p&gt;


&lt;div class=&quot;kh-demo&quot; data-pagefind-ignore&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p&amp;gt;
  Posts per year, 2017–2020:
  &amp;lt;span class=&quot;chart&quot; aria-hidden=&quot;true&quot;&amp;gt;{b:60,70,40,40}&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;table class=&quot;kh-u-sr-only&quot;&amp;gt;
  &amp;lt;caption class=&quot;kh-u-sr-only&quot;&amp;gt;Posts per year, 2017–2020&amp;lt;/caption&amp;gt;
  &amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th scope=&quot;col&quot;&amp;gt;Year&amp;lt;/th&amp;gt;&amp;lt;th scope=&quot;col&quot;&amp;gt;Posts&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;2017&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;6&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;2018&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;7&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;2019&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;4&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;2020&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;4&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;kh-demo__live&quot;&gt;&lt;p&gt;
  Posts per year, 2017–2020:
  &lt;span class=&quot;chart&quot; aria-hidden=&quot;true&quot;&gt;{b:60,70,40,40}&lt;/span&gt;
&lt;/p&gt;
&lt;table class=&quot;kh-u-sr-only&quot;&gt;
  &lt;caption class=&quot;kh-u-sr-only&quot;&gt;Posts per year, 2017–2020&lt;/caption&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Year&lt;/th&gt;&lt;th scope=&quot;col&quot;&gt;Posts&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;2017&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;2018&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;2019&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;2020&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;i-want-an-excuse-to-use-it&quot; tabindex=&quot;-1&quot;&gt;I want an excuse to use it&lt;a href=&quot;#i-want-an-excuse-to-use-it&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My enthusiasm for variable fonts as an expressive runtime is sitting at about &lt;span class=&quot;chart&quot; aria-hidden=&quot;true&quot;&gt;{p:85}&lt;/span&gt;&lt;span class=&quot;kh-u-sr-only&quot;&gt;(roughly 85 percent)&lt;/span&gt;, while my likelihood of actually reaching for Datatype on the next chart I ship is somewhere around &lt;span class=&quot;chart&quot; aria-hidden=&quot;true&quot;&gt;{p:25}&lt;/span&gt;&lt;span class=&quot;kh-u-sr-only&quot;&gt;(roughly 25 percent)&lt;/span&gt;. The gap between those two pies is basically the whole post.&lt;/p&gt;
&lt;p&gt;Frank&#39;s &lt;a href=&quot;https://franktisellano.github.io/datatype/&quot;&gt;specimen site&lt;/a&gt; is worth a visit on its own; the variable-axis sliders and live examples are a small showcase of typographic care.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://franktisellano.github.io/datatype/&quot; class=&quot;kh-button&quot;&gt;Try Datatype on the project site &amp;rarr;&lt;/a&gt;&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>Building a virtual garage for hobby projects</title>
    <link href="https://www.allaboutken.com/posts/20260509-vercel-proxy-hobby-projects/"/>
    <published>2026-05-09T00:00:00.000Z</published>
    <updated>2026-05-09T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260509-vercel-proxy-hobby-projects/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="cloud infrastructure" />
    <category term="web development" />
    <category term="static sites" />
    <summary type="html">I wanted my GitHub Pages hobby projects on-domain without a complicated workflow; Vercel rewrites turned out to be the cleanest path.</summary>
    <content type="html">
&lt;p&gt;I&#39;ve been building hobby projects for years. Normally I&#39;ll give the better ones a write-up here and a link to GitHub Pages, but the project itself lives at &lt;code&gt;khawkins98.github.io/project-name/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I wanted to bring them on-domain without the obvious headaches: dozens of subdomains is more infrastructure than any of these deserve, subdomains still confuse people, and copying build output into the main repo defeats the point of keeping them separate.&lt;/p&gt;
&lt;p&gt;Could I just have my site do what GitHub Pages already does: proxy &lt;code&gt;/repo-name/&lt;/code&gt; to whatever GitHub is already serving?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Before: &lt;code&gt;khawkins98.github.io/PDF-A-go-go&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;After: &lt;code&gt;AllAboutKen.com/PDF-A-go-go&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same project, same deploy pipeline, better home.&lt;/p&gt;
&lt;p&gt;
  &lt;a href=&quot;https://www.allaboutken.com/garage/&quot; class=&quot;kh-button&quot;&gt;See the garage&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Keep reading if you want to see exactly how I wired it up.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h2 id=&quot;tldr&quot; tabindex=&quot;-1&quot;&gt;tl;dr&lt;a href=&quot;#tldr&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Vercel &lt;code&gt;rewrites&lt;/code&gt; proxy &lt;code&gt;/project-name/:path*&lt;/code&gt; to the GitHub Pages origin — no deploy changes required&lt;/li&gt;
&lt;li&gt;A naive rewrite 404s on the project root; two &lt;code&gt;redirects&lt;/code&gt; entries (bare path and trailing slash → &lt;code&gt;/project-name/index.html&lt;/code&gt;) fix it&lt;/li&gt;
&lt;li&gt;Each project keeps its GitHub Pages URL; a hostname-check script redirects those visitors to the canonical URL&lt;/li&gt;
&lt;li&gt;CORS headers on proxied paths cover shared JS assets loaded via &lt;code&gt;fetch()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;/garage/&lt;/code&gt; index page on the main site ties all the projects together&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;what-i-didnt-want-to-do&quot; tabindex=&quot;-1&quot;&gt;What I didn&#39;t want to do&lt;a href=&quot;#what-i-didnt-want-to-do&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The obvious path is a merge: monorepo, or move each project to Vercel as a separate deployment. Both mean real work per project: consolidated CI, shared dependency trees, combined issue trackers. Hobby projects earn their independence precisely because they&#39;re free to be messy and self-contained. The right fix is a smarter routing layer in front, not a restructuring underneath.&lt;/p&gt;
&lt;h2 id=&quot;how-the-proxy-works&quot; tabindex=&quot;-1&quot;&gt;How the proxy works&lt;a href=&quot;#how-the-proxy-works&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My site was already on Vercel and each project already served from &lt;code&gt;https://khawkins98.github.io/repo-name/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Vercel &lt;code&gt;rewrite&lt;/code&gt; rules can proxy a path prefix to an external origin. Minimum viable &lt;code&gt;vercel.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;rewrites&amp;quot;: [
    {
      &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go/:path*&amp;quot;,
      &amp;quot;destination&amp;quot;: &amp;quot;https://khawkins98.github.io/PDF-A-go-go/:path*&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Requests to &lt;code&gt;allaboutken.com/PDF-A-go-go/anything&lt;/code&gt; forward to &lt;code&gt;khawkins98.github.io/PDF-A-go-go/anything&lt;/code&gt;. The GitHub Pages deploy keeps working at its original URL unchanged.&lt;/p&gt;
&lt;p&gt;This works for every path except the project root.&lt;/p&gt;
&lt;h2 id=&quot;the-trailing-slash-problem&quot; tabindex=&quot;-1&quot;&gt;The trailing-slash problem&lt;a href=&quot;#the-trailing-slash-problem&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Going to &lt;code&gt;allaboutken.com/PDF-A-go-go/&lt;/code&gt; still gave a 404, even with the rewrite in place. The reason is Vercel&#39;s processing order: filesystem matching (including a directory-index check for &lt;code&gt;build/PDF-A-go-go/index.html&lt;/code&gt;) runs before rewrites. That file doesn&#39;t exist in the main site&#39;s build, so Vercel 404s before the rewrite ever fires.&lt;/p&gt;
&lt;p&gt;Two &lt;code&gt;redirects&lt;/code&gt; entries fix it. Redirects run before filesystem matching:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;redirects&amp;quot;: [
    { &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go&amp;quot;,  &amp;quot;destination&amp;quot;: &amp;quot;/PDF-A-go-go/index.html&amp;quot;, &amp;quot;permanent&amp;quot;: false },
    { &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go/&amp;quot;, &amp;quot;destination&amp;quot;: &amp;quot;/PDF-A-go-go/index.html&amp;quot;, &amp;quot;permanent&amp;quot;: false }
  ],
  &amp;quot;rewrites&amp;quot;: [
    {
        &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go/:path*&amp;quot;,
        &amp;quot;destination&amp;quot;: &amp;quot;https://khawkins98.github.io/PDF-A-go-go/:path*&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visiting &lt;code&gt;/PDF-A-go-go/&lt;/code&gt; now redirects (302) to &lt;code&gt;/PDF-A-go-go/index.html&lt;/code&gt;. No directory-index ambiguity; the rewrite fires and proxies to GitHub Pages. The browser ends up at &lt;code&gt;/PDF-A-go-go/index.html&lt;/code&gt;, where relative asset paths like &lt;code&gt;./app.js&lt;/code&gt; resolve correctly to &lt;code&gt;/PDF-A-go-go/app.js&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;not-burning-the-old-address&quot; tabindex=&quot;-1&quot;&gt;Not burning the old address&lt;a href=&quot;#not-burning-the-old-address&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GitHub Pages keeps serving at &lt;code&gt;khawkins98.github.io/project-name/&lt;/code&gt;. The Vercel setup is additive: the personal domain gains a route, the original URL stays live. Rolling back is two deleted lines in &lt;code&gt;vercel.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For now I&#39;ve only moved two projects across — PDF-A-go-go and pinment — while I confirm that existing embed links aren&#39;t affected. The redirect from &lt;code&gt;khawkins98.github.io&lt;/code&gt; to the proxied URL could in theory break things for anyone who has embedded the old URL directly. Once it&#39;s clear nothing is breaking, adding the rest is a few lines in &lt;code&gt;vercel.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But visitors arriving at the old URL should reach the canonical one. A small script at the top of each project&#39;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script&amp;gt;if(window.location.hostname===&#39;khawkins98.github.io&#39;){window.location.replace(&#39;https://www.allaboutken.com/PDF-A-go-go/&#39;)}&amp;lt;/script&amp;gt;
&amp;lt;link rel=&amp;quot;canonical&amp;quot; href=&amp;quot;https://www.allaboutken.com/PDF-A-go-go/&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hostname check is essential. Without it, the redirect fires on the proxied page too: allaboutken.com proxies to github.io, github.io redirects back to allaboutken.com, repeat. The check breaks the loop.&lt;/p&gt;
&lt;h2 id=&quot;dont-forget-cors-for-shared-js-assets&quot; tabindex=&quot;-1&quot;&gt;Don&#39;t forget: CORS for shared JS assets&lt;a href=&quot;#dont-forget-cors-for-shared-js-assets&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260225-introducing-pinment/&quot;&gt;Pinment&lt;/a&gt; is a bookmarklet tool. Its install snippet builds a script URL from &lt;code&gt;window.location.origin&lt;/code&gt; at runtime, so it always points to whichever domain the user is visiting from. Through the Vercel proxy, that becomes &lt;code&gt;allaboutken.com/pinment/pinment-bookmarklet.js&lt;/code&gt;, which the rewrite proxies correctly to GitHub Pages.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script src&amp;gt;&lt;/code&gt; tags don&#39;t trigger CORS, but &lt;code&gt;fetch()&lt;/code&gt; does. A &lt;code&gt;headers&lt;/code&gt; block in &lt;code&gt;vercel.json&lt;/code&gt; covers it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;headers&amp;quot;: [
    { &amp;quot;source&amp;quot;: &amp;quot;/pinment/:path*&amp;quot;, &amp;quot;headers&amp;quot;: [{ &amp;quot;key&amp;quot;: &amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;*&amp;quot; }] }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Worth adding for any project that exposes public JS assets loaded programmatically from other pages.&lt;/p&gt;
&lt;h2 id=&quot;the-full-configuration&quot; tabindex=&quot;-1&quot;&gt;The full configuration&lt;a href=&quot;#the-full-configuration&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;buildCommand&amp;quot;: &amp;quot;yarn build&amp;quot;,
  &amp;quot;outputDirectory&amp;quot;: &amp;quot;build&amp;quot;,
  &amp;quot;headers&amp;quot;: [
    { &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go/:path*&amp;quot;, &amp;quot;headers&amp;quot;: [{ &amp;quot;key&amp;quot;: &amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;*&amp;quot; }] },
    { &amp;quot;source&amp;quot;: &amp;quot;/pinment/:path*&amp;quot;, &amp;quot;headers&amp;quot;: [{ &amp;quot;key&amp;quot;: &amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;*&amp;quot; }] }
  ],
  &amp;quot;redirects&amp;quot;: [
    { &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go&amp;quot;,  &amp;quot;destination&amp;quot;: &amp;quot;/PDF-A-go-go/index.html&amp;quot;, &amp;quot;permanent&amp;quot;: false },
    { &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go/&amp;quot;, &amp;quot;destination&amp;quot;: &amp;quot;/PDF-A-go-go/index.html&amp;quot;, &amp;quot;permanent&amp;quot;: false },
    { &amp;quot;source&amp;quot;: &amp;quot;/pinment&amp;quot;,  &amp;quot;destination&amp;quot;: &amp;quot;/pinment/index.html&amp;quot;, &amp;quot;permanent&amp;quot;: false },
    { &amp;quot;source&amp;quot;: &amp;quot;/pinment/&amp;quot;, &amp;quot;destination&amp;quot;: &amp;quot;/pinment/index.html&amp;quot;, &amp;quot;permanent&amp;quot;: false }
  ],
  &amp;quot;rewrites&amp;quot;: [
    { &amp;quot;source&amp;quot;: &amp;quot;/PDF-A-go-go/:path*&amp;quot;, &amp;quot;destination&amp;quot;: &amp;quot;https://khawkins98.github.io/PDF-A-go-go/:path*&amp;quot; },
    { &amp;quot;source&amp;quot;: &amp;quot;/pinment/:path*&amp;quot;, &amp;quot;destination&amp;quot;: &amp;quot;https://khawkins98.github.io/pinment/:path*&amp;quot; }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;adding-another-project&quot; tabindex=&quot;-1&quot;&gt;Adding another project&lt;a href=&quot;#adding-another-project&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Confirm GitHub Pages is enabled and serving at &lt;code&gt;USERNAME.github.io/repo-name/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check asset paths: &lt;code&gt;./&lt;/code&gt;-relative or prefixed with the repo name. Bare &lt;code&gt;/styles.css&lt;/code&gt; will break.&lt;/li&gt;
&lt;li&gt;Add two &lt;code&gt;redirects&lt;/code&gt; entries and one &lt;code&gt;rewrite&lt;/code&gt; to &lt;code&gt;vercel.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the hostname-check script and &lt;code&gt;&amp;lt;link rel=&amp;quot;canonical&amp;quot;&amp;gt;&lt;/code&gt; to the project&#39;s &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the project to the &lt;a href=&quot;https://www.allaboutken.com/garage/&quot;&gt;garage page&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For SPAs with client-side routing: the project needs a &lt;code&gt;404.html&lt;/code&gt; on GitHub Pages that loads &lt;code&gt;index.html&lt;/code&gt;. The proxy can&#39;t route to paths the GH Pages deploy doesn&#39;t know about.&lt;/p&gt;
&lt;p&gt;The full configuration and checklist live in &lt;a href=&quot;https://github.com/khawkins98/allaboutken-11ty/blob/main/vercel.json&quot;&gt;&lt;code&gt;vercel.json&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/khawkins98/allaboutken-11ty/blob/main/docs/DEPLOYMENT.md&quot;&gt;&lt;code&gt;docs/DEPLOYMENT.md&lt;/code&gt;&lt;/a&gt;. Nothing about the project deploys has to change: GitHub Pages stays the origin, Vercel becomes the routing layer. See what it looks like in practice at the &lt;a href=&quot;https://www.allaboutken.com/garage/&quot;&gt;garage&lt;/a&gt;. If you&#39;ve proxied projects this way — or found a cleaner approach — I&#39;d be glad to hear about it. The projects just get to live at home.&lt;/p&gt;
&lt;p&gt;I think this is a clean way to bring web projects back home without adding server complexity.&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>Eye-tracking heatmaps in your browser, no lab needed</title>
    <link href="https://www.allaboutken.com/posts/20260503-foveacast/"/>
    <published>2026-05-03T00:00:00.000Z</published>
    <updated>2026-05-03T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260503-foveacast/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="open source" />
    <category term="developer tools" />
    <category term="data visualization" />
    <category term="UX design" />
    <summary type="html">Stop guessing whether users will notice your CTA. Foveacast predicts attention from a screenshot, entirely in your browser, nothing uploaded.</summary>
    <content type="html">
&lt;p&gt;At some point in most design or content reviews you find yourself trying to answer a question you can&#39;t answer cleanly: not &amp;quot;is it pretty?&amp;quot; but &amp;quot;will users actually notice the call to action, or are they going to get distracted by the hero image first?&amp;quot;&lt;/p&gt;
&lt;p&gt;The honest answer is usually &amp;quot;Based off my knowledge and experience: I think so.&amp;quot; That only holds until a stakeholder who disagrees says the same thing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://khawkins98.github.io/Foveacast/&quot;&gt;Foveacast&lt;/a&gt; gives both of you something to look at instead. Drop a screenshot of a UI — a web page, an app screen, a mockup — and it produces a predicted attention heatmap: a color overlay showing where a typical user is likely to look, and how that attention evolves over time. Nothing leaves your machine; the model runs entirely in your browser.&lt;/p&gt;

&lt;p&gt;
  &lt;a href=&quot;https://khawkins98.github.io/Foveacast/&quot; class=&quot;kh-button&quot;&gt;Try Foveacast&lt;/a&gt;
  &lt;a href=&quot;https://github.com/khawkins98/Foveacast&quot; class=&quot;kh-button kh-button--sm&quot;&gt;View on GitHub&lt;/a&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drop a UI screenshot, get a predicted attention heatmap with fixation sequence, attention zones, and centroid trajectory.&lt;/li&gt;
&lt;li&gt;Three viewing durations: first glance (1 s), quick scan (3 s), full viewing (7 s) — modeled separately, so you can see how attention evolves as users spend more time on the page.&lt;/li&gt;
&lt;li&gt;Runs entirely in your browser. The model (~57 MB per duration) downloads and caches on first use; nothing is ever uploaded.&lt;/li&gt;
&lt;li&gt;Built on MSI-Net fine-tuned on real UI eye-tracking data. Not a natural-scene model that doesn&#39;t know what a button is.&lt;/li&gt;
&lt;li&gt;It&#39;s a probabilistic estimate, not a user study. The reading-your-results guide in the tool covers what it can and can&#39;t tell you.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-analysis-report&quot; tabindex=&quot;-1&quot;&gt;The analysis report&lt;a href=&quot;#the-analysis-report&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The output is a scrollable analysis report.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://www.allaboutken.com/images/blog/foveacast-heatmap-overlay.jpg&quot; alt=&quot;Predicted attention heatmap overlaid on the allaboutken.com homepage. Warm orange and yellow colors concentrate on the site header and the first content heading; the lower body of the page shows cooler blue tones indicating lower predicted attention.&quot;&gt;
  &lt;figcaption&gt;Warm colors show where a typical user&#39;s attention is predicted to concentrate. Cool tones indicate lower predicted attention density.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The heatmap overlay uses the inferno colormap: warm colors where attention is predicted to concentrate. Three viewing durations (first glance, quick scan, full viewing) are each backed by a separate model.&lt;/p&gt;
&lt;p&gt;Below the heatmap: a rule-of-thirds grid with attention shares per region, a fixation sequence showing the predicted scan path as numbered dots, attention zones with contour rings at 10%, 25%, and 50% attention mass, and a centroid trajectory tracking where the attention center moves from first glance to sustained viewing.&lt;/p&gt;
&lt;p&gt;In a design review, &amp;quot;the CTA doesn&#39;t appear until fixation 5, and 42% of initial attention lands on the hero image&amp;quot; is a different kind of conversation than &amp;quot;I think users will miss it.&amp;quot; It&#39;s not proof. But it&#39;s a concrete starting point, and it gets you past the stage where everyone is arguing from intuition.&lt;/p&gt;
&lt;p&gt;It&#39;s also useful as a self-check before publishing. Before you ship a redesigned landing page and find out three weeks later that the primary action was invisible.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://www.allaboutken.com/images/blog/foveacast-scan-paths.jpg&quot; alt=&quot;Two side-by-side strips: on the left, a fixation sequence showing numbered dots tracking the predicted scan path across the page; on the right, attention zones with concentric contour rings marking the 10%, 25%, and 50% attention mass boundaries.&quot;&gt;
  &lt;figcaption&gt;Fixation sequence (left) and attention zones (right). The numbered dots show the predicted scan path order; contour rings mark where the densest fraction of attention falls.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;trained-on-real-ui-gaze-data-not-natural-scenes&quot; tabindex=&quot;-1&quot;&gt;Trained on real UI gaze data, not natural scenes&lt;a href=&quot;#trained-on-real-ui-gaze-data-not-natural-scenes&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most open-source saliency models are trained on the &lt;a href=&quot;http://salicon.net/&quot;&gt;SALICON dataset&lt;/a&gt;: natural photographs scraped from Flickr. Those models have learned to predict attention on photos of mountains and faces and street scenes. Put a web page in front of them and they produce a diffuse centrality blob. They don&#39;t know that &amp;quot;Start free trial&amp;quot; is the most important element on the screen; they see a rectangular region with text and mild contrast and model it accordingly.&lt;/p&gt;

&lt;figure class=&quot;kh-bleed-out&quot;&gt;
  &lt;img src=&quot;https://www.allaboutken.com/images/blog/foveacast-comparison-nytimes.jpg&quot; alt=&quot;Three-panel comparison using a New York Times page: the original screenshot, a SALICON heatmap showing a diffuse central blob with no meaningful structure, and the Foveacast heatmap concentrating attention on the masthead, primary headline, and article bylines.&quot;&gt;
  &lt;figcaption&gt;Same page, two models. SALICON — trained on natural photographs — produces a diffuse central blob that poorly &quot;understands&quot; content. Foveacast, fine-tuned on real UI eye-tracking data, concentrates predicted attention on the headline, navigation, and key text elements.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/khawkins98/foveacast-training&quot;&gt;foveacast-training&lt;/a&gt; pipeline fine-tuned &lt;a href=&quot;https://github.com/alexanderkroner/saliency&quot;&gt;MSI-Net&lt;/a&gt; (Kroner et al. 2020) on &lt;a href=&quot;https://doi.org/10.1145/3544548.3581096&quot;&gt;UEyes&lt;/a&gt; (Jiang et al. 2023). Much credit is due there: Jiang et al. collected 1,980 UI screenshots with real eye-tracking data from 62 participants across desktop, mobile, web, and poster UIs, then published the full dataset on &lt;a href=&quot;https://zenodo.org/records/8010312&quot;&gt;Zenodo&lt;/a&gt; under &lt;a href=&quot;https://creativecommons.org/licenses/by/4.0/&quot;&gt;CC BY 4.0&lt;/a&gt;. Without that open release, Foveacast wouldn&#39;t have been viable to build. Fixations were recorded at 1, 3, and 7 seconds of viewing time, which is why there are three separate models rather than one. The gaze pattern at one second (&amp;quot;where do my eyes go first?&amp;quot;) and at seven seconds (&amp;quot;where does sustained attention end up?&amp;quot;) are genuinely different questions with different answers.&lt;/p&gt;
&lt;p&gt;Fine-tuning on UI content closes the gap. On a held-out test split, the fine-tuned model improves by +43% on correlation coefficient and cuts KL divergence by 44% compared to the stock SALICON-only baseline. The stock model produces diffuse blobs. The fine-tuned model picks up navigation elements, content headings, and interactive controls, matching where real users actually looked.&lt;/p&gt;
&lt;h2 id=&quot;limitations-what-predicted-attention-cant-tell-you&quot; tabindex=&quot;-1&quot;&gt;Limitations: what predicted attention can&#39;t tell you&lt;a href=&quot;#limitations-what-predicted-attention-cant-tell-you&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Foveacast shows a non-dismissible note in the UI that says this is a probabilistic estimate based on population-average gaze patterns. A few things worth keeping in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It&#39;s not measured data.&lt;/strong&gt; The heatmap is model output, not fixations from real users looking at your specific design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Training distribution.&lt;/strong&gt; UEyes is primarily Western-language desktop and mobile UI, collected prior to the 2023 publication. Right-to-left layouts, dark-mode UIs, and design patterns that weren&#39;t common then may produce less reliable estimates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not a click predictor.&lt;/strong&gt; High predicted attention on an element doesn&#39;t mean users will interact with it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need real eye-tracking data — measured fixations from real participants — there are commercial services: &lt;a href=&quot;https://attentioninsight.com&quot;&gt;Attention Insight&lt;/a&gt;, &lt;a href=&quot;https://www.alpha.one/products/expoze-io&quot;&gt;Alpha.one (formerly expoze.io)&lt;/a&gt;, &lt;a href=&quot;https://clueify.com&quot;&gt;Clueify&lt;/a&gt;. They&#39;re paid, and they send your screenshots to a third party.&lt;/p&gt;
&lt;p&gt;Foveacast is great to quickly, freely and securely close the gap: faster than a study, more specific than the &lt;a href=&quot;https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content/&quot;&gt;F-pattern heuristic&lt;/a&gt;, and more honest than &amp;quot;I think.&amp;quot;&lt;/p&gt;
&lt;h2 id=&quot;how-it-runs-entirely-in-your-browser&quot; tabindex=&quot;-1&quot;&gt;How it runs entirely in your browser&lt;a href=&quot;#how-it-runs-entirely-in-your-browser&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like &lt;a href=&quot;https://www.allaboutken.com/posts/20260429-svgomg-font/&quot;&gt;SVGOMG-Font&lt;/a&gt; and &lt;a href=&quot;https://www.allaboutken.com/posts/20260216-introducing-pdf-a-go-slim/&quot;&gt;PDF-A-go-slim&lt;/a&gt;, everything processes locally. Design screenshots often contain unreleased client work or internal materials. Uploading them to a third-party service to get a heatmap is friction many organisations can&#39;t accept.&lt;/p&gt;
&lt;p&gt;Running ONNX inference in the browser at this scale had some practical problems. WASM threading needs cross-origin isolation, which requires a service worker injecting the right headers. ORT Web&#39;s WASM paths have to be pinned when the app serves from a GitHub Pages subpath. The model weights are gitignored and fetched from a GitHub Release at deploy time rather than committed to the repo (57 MB each across three duration variants; they&#39;d bloat every clone). The full engineering log is in &lt;a href=&quot;https://github.com/khawkins98/Foveacast/blob/main/LEARNINGS.md&quot;&gt;LEARNINGS.md&lt;/a&gt; if you want the details.&lt;/p&gt;
&lt;h2 id=&quot;training-on-consumer-hardware-a-personal-note&quot; tabindex=&quot;-1&quot;&gt;Training on consumer hardware: a personal note&lt;a href=&quot;#training-on-consumer-hardware-a-personal-note&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This was my first proper model training exercise. I&#39;d worked with embeddings before, but never fine-tuned a vision model. A few things made it more approachable than expected.&lt;/p&gt;
&lt;p&gt;The training ran entirely on a MacBook Air M4 — no cloud GPU, no cluster. About three hours per model, so roughly nine hours of compute total across the three duration variants. Slow in absolute terms, but not the data-centre job I&#39;d assumed fine-tuning required.&lt;/p&gt;
&lt;p&gt;The other difference was AI-assisted development. What I&#39;d expected to be a multi-week slog — working through PyTorch docs, debugging ONNX export paths, figuring out why the WASM runtime was choking on FP16 weights — turned into a genuinely enjoyable weekend project. Claude and Copilot handled a lot of the &amp;quot;read the error and suggest a fix&amp;quot; loop that normally drains the fun out of this kind of work.&lt;/p&gt;
&lt;p&gt;I learned a lot and I&#39;d do it again. If you want to fine-tune a saliency model on your own domain-specific data, the &lt;a href=&quot;https://github.com/khawkins98/foveacast-training&quot;&gt;training pipeline is open&lt;/a&gt; and the README documents what worked and what didn&#39;t.&lt;/p&gt;
&lt;h2 id=&quot;try-foveacast-on-your-own-ui&quot; tabindex=&quot;-1&quot;&gt;Try Foveacast on your own UI&lt;a href=&quot;#try-foveacast-on-your-own-ui&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re working on a UI and want something more specific than the F-pattern heuristic and faster than an eye-tracking study, &lt;a href=&quot;https://khawkins98.github.io/Foveacast/&quot;&gt;Foveacast&lt;/a&gt; is a reasonable starting point. If you use it in a real design review — or find a case where the model is clearly wrong — I&#39;d genuinely like to hear about it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://khawkins98.github.io/Foveacast/&quot;&gt;Foveacast is on GitHub Pages&lt;/a&gt;. The source is &lt;a href=&quot;https://github.com/khawkins98/Foveacast&quot;&gt;on GitHub&lt;/a&gt;, with the training pipeline in a &lt;a href=&quot;https://github.com/khawkins98/foveacast-training&quot;&gt;companion repository&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Related posts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260429-svgomg-font/&quot;&gt;Stop converting SVG text to outlines&lt;/a&gt; — another browser-only utility from the same build season&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260216-90s-desktop-paradigm-browser-utilities/&quot;&gt;Your browser utility wants to be a floating palette&lt;/a&gt; — the &amp;quot;do one thing, entirely in the browser&amp;quot; design philosophy&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260302-semantic-search-browser-embeddings/&quot;&gt;Semantic search on a static site, no API keys required&lt;/a&gt; — the same pattern of ML inference running locally in the browser&lt;/li&gt;
&lt;/ul&gt;

</content>
  </entry>
  <entry>
    <title>Stop converting SVG text to outlines</title>
    <link href="https://www.allaboutken.com/posts/20260429-svgomg-font/"/>
    <published>2026-04-29T00:00:00.000Z</published>
    <updated>2026-04-29T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260429-svgomg-font/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="typography" />
    <category term="web development" />
    <category term="open source" />
    <category term="accessibility" />
    <summary type="html">Converting SVG text to outlines breaks accessibility, search, and translation. SVGOMG-Font embeds a subsetted font instead, in your browser.</summary>
    <content type="html">
&lt;p&gt;
  &lt;a href=&quot;https://khawkins98.github.io/svgomg-font/&quot; class=&quot;kh-button&quot;&gt;Check out SVGOMG-Font&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Every so often I need to put an SVG with text on a website: an infographic, a diagram, a data visualisation with labels. Each time, I end up staring at the same bad choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option A:&lt;/strong&gt; Convert the text to outlines. The SVG renders identically everywhere. No font loading, no layout surprises. Done. But now the text is a pile of path data. Screen readers see nothing. Search engines see nothing. Language models see nothing. Translation is impossible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option B:&lt;/strong&gt; Leave the text as text. Clean, accessible, editable. But on any device that doesn&#39;t have the font installed, the browser falls back to whatever comes first in the system stack, and your carefully spaced infographic turns into a broken layout that looks like someone used Times New Roman on a web 1.0 site.&lt;/p&gt;
&lt;p&gt;I&#39;ve been choosing Option A for years and feeling bad about it. Despite plenty of searching, nothing combined what I actually wanted: a tool that runs in the browser, doesn&#39;t upload anywhere, and is open source. The closest matches were CLIs or a paid cloud service. So I built &lt;a href=&quot;https://khawkins98.github.io/svgomg-font/&quot;&gt;SVGOMG-Font&lt;/a&gt;. Drop the SVG in, get the same file back with the font subsetted to the characters in use and embedded as a base64 data URI.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drop an SVG in, get back a self-contained one with the font embedded. Runs entirely in the browser, nothing uploaded.&lt;/li&gt;
&lt;li&gt;Converting SVG text to outlines makes it invisible to assistive tech, search, translation, and LLMs.&lt;/li&gt;
&lt;li&gt;Leaving text as-is fails on devices without the font installed.&lt;/li&gt;
&lt;li&gt;SVGOMG-Font subsets to only the glyphs you actually use, so file sizes stay sensible.&lt;/li&gt;
&lt;li&gt;Works cleanly for general-purpose infographics, diagrams, and posters. Rich text layouts that lean on OpenType features (ligatures, contextual alternates, complex scripts) need a closer look.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&#39;s an SVG set in &lt;a href=&quot;https://fonts.google.com/specimen/Playwrite+DE+SAS&quot;&gt;Playwrite DE SAS&lt;/a&gt;, a connected German school-handwriting script almost no reader will have installed:&lt;/p&gt;




&lt;div class=&quot;kh-svgomg-compare&quot;&gt;
  &lt;figure&gt;
    &lt;object data=&quot;/images/blog/svgomg-font-playwrite-broken.svg&quot; type=&quot;image/svg+xml&quot; aria-label=&quot;A stylized note that reads &#39;Thank you for shipping Q3&#39; rendering in the browser default serif. The SVG references Playwrite DE SAS but doesn&#39;t embed it.&quot;&gt;&lt;/object&gt;
    &lt;figcaption&gt;&lt;strong&gt;Before.&lt;/strong&gt; No font embedded. Most readers see a Times-like serif fallback.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;object data=&quot;/images/blog/svgomg-font-playwrite-embedded.svg&quot; type=&quot;image/svg+xml&quot; aria-label=&quot;The same stylized note, this time rendering correctly in Playwrite DE SAS as connected cursive handwriting because the font is embedded inside the SVG as a base64 data URI.&quot;&gt;&lt;/object&gt;
    &lt;figcaption&gt;&lt;strong&gt;After.&lt;/strong&gt; Font subsetted and embedded as base64; about 30 KB total.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;

&lt;p&gt;The text is still real text: selectable, searchable, translatable. The font travels with the file, so it renders the same wherever the SVG lands.&lt;/p&gt;
&lt;p&gt;You can sort of sidestep this if you control the host page: load the font via the page&#39;s CSS, and the inline SVG inherits it. But that&#39;s another asset to manage, another caching layer, and it only holds while the SVG stays on that page. Once the file has to travel (attached to an email, dropped into someone else&#39;s CMS, sent over Slack) the font reference goes dead. Bake the font into the SVG itself, the way a PDF embeds fonts, and it just works wherever the file ends up.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-outlines&quot; tabindex=&quot;-1&quot;&gt;The problem with outlines&lt;a href=&quot;#the-problem-with-outlines&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you tell Illustrator or Figma to &amp;quot;create outlines,&amp;quot; it replaces every character with the equivalent vector paths. The rendering is locked in. But you&#39;ve also just destroyed everything that made the text text:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;: screen readers can&#39;t read paths. The text is gone from the accessibility tree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt;: both on-page (Cmd+F) and crawlers. An SVG infographic full of outlined text is invisible to search engines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translation&lt;/strong&gt;: auto-translate and localisation tools operate on text nodes. No text nodes, no translation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLMs&lt;/strong&gt;: if a language model needs to understand what your diagram says, it now has to do OCR or get the content from somewhere else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Editability&lt;/strong&gt;: editing outlined text means going back to the design tool, making the change, re-exporting, re-optimising, re-deploying. Minor corrections become multi-step processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&#39;s a file-size argument too. A few hundred outlined characters easily adds tens of kilobytes of path data. Subsetting the font is usually smaller than outlining it.&lt;/p&gt;

&lt;p&gt;
  &lt;a href=&quot;https://khawkins98.github.io/svgomg-font/&quot; class=&quot;kh-button&quot;&gt;Try SVGOMG-Font&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How it works&lt;a href=&quot;#how-it-works&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The structural change is small. Before, an &lt;code&gt;@font-face&lt;/code&gt; rule points at a remote URL the viewer may never load:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;style&amp;gt;
  @font-face {
    font-family: &#39;Playwrite DE SAS&#39;;
    src: url(&#39;https://fonts.example/playwritedesas-regular.woff2&#39;);
  }
&amp;lt;/style&amp;gt;
&amp;lt;text font-family=&amp;quot;Playwrite DE SAS&amp;quot; x=&amp;quot;20&amp;quot; y=&amp;quot;40&amp;quot;&amp;gt;Hello, world&amp;lt;/text&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After, the same rule, with the font subsetted and inlined as a data URI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;style&amp;gt;
  @font-face {
    font-family: &#39;Playwrite DE SAS&#39;;
    src: url(&#39;data:font/woff2;base64,d09GMgABAAAA…&#39;) format(&#39;woff2&#39;);
  }
&amp;lt;/style&amp;gt;
&amp;lt;text font-family=&amp;quot;Playwrite DE SAS&amp;quot; x=&amp;quot;20&amp;quot; y=&amp;quot;40&amp;quot;&amp;gt;Hello, world&amp;lt;/text&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s the whole shape of it. SVGOMG-Font scans your file for &lt;code&gt;@font-face&lt;/code&gt; references, fetches each font, subsets it to the glyphs in use, and rewrites the rule. No installation, no account, no upload: it all runs in the browser.&lt;/p&gt;
&lt;h2 id=&quot;why-browser-only-matters&quot; tabindex=&quot;-1&quot;&gt;Why browser-only matters&lt;a href=&quot;#why-browser-only-matters&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SVGs with fonts are often used in sensitive contexts: client work, unreleased materials, internal tools. Uploading to a third-party service to fix font rendering is a non-starter in a lot of organisations.&lt;/p&gt;
&lt;p&gt;SVGOMG-Font processes everything locally. Nothing leaves your machine. It&#39;s a single static page. &lt;a href=&quot;https://khawkins98.github.io/Foveacast/&quot;&gt;Foveacast&lt;/a&gt;, my browser-only attention-heatmap tool, runs the same way for the same reasons (write-up coming).&lt;/p&gt;
&lt;h2 id=&quot;prior-art&quot; tabindex=&quot;-1&quot;&gt;Prior art&lt;a href=&quot;#prior-art&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To be fair, this isn&#39;t entirely new territory. A handful of tools cover the same ground:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BTBurke/svg-embed-font&quot;&gt;svg-embed-font&lt;/a&gt;, Go CLI: base64-embeds the full font.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/phauer/svg-buddy&quot;&gt;svg-buddy&lt;/a&gt;, Java CLI: WOFF2 embedding given a local font file.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pedrovhb/svgfontembed&quot;&gt;svgfontembed&lt;/a&gt;, Python CLI: embeds and subsets.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vecta.io/nano&quot;&gt;Vecta.io Nano&lt;/a&gt;: browser drag-and-drop SVG compressor that also handles font subsetting and embedding, hosted as a cloud service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The CLIs share the install gap. Vecta.io Nano is the closest match in spirit (same drop-the-SVG-in workflow) but it&#39;s closed-source freemium SaaS, processes your file server-side, and has free-tier limits on file count and size. SVGOMG-Font fills the gap I kept hitting: same drop-in workflow, but open source, fully client-side, no upload, no signup. It also auto-resolves fonts from Google Fonts and Fontsource URLs, and on Chromium can pull from installed system fonts via the Local Font Access API.&lt;/p&gt;
&lt;h2 id=&quot;the-subsetting-part&quot; tabindex=&quot;-1&quot;&gt;The subsetting part&lt;a href=&quot;#the-subsetting-part&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The naive approach (embed the whole font) works but produces large files. A single Latin-subset woff2 file is roughly 20 to 30 KB, base64-encoded to about 40 KB. Two weights, and you&#39;ve added 80 KB to your SVG before doing anything else.&lt;/p&gt;
&lt;p&gt;SVGOMG-Font subsets to the characters in the SVG&#39;s text nodes, plus a Basic Latin safety baseline (U+0020 to U+007E). For a typical infographic with a few hundred unique characters, the subset is usually 5 to 15 KB encoded. Often smaller than outlining the same text.&lt;/p&gt;
&lt;p&gt;There&#39;s a toggle to skip subsetting, useful when text gets added or replaced after export.&lt;/p&gt;
&lt;h2 id=&quot;what-it-handles&quot; tabindex=&quot;-1&quot;&gt;What it handles&lt;a href=&quot;#what-it-handles&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Fonts referenced via Google Fonts CSS URLs&lt;/li&gt;
&lt;li&gt;Fonts referenced via Fontsource CDN paths&lt;/li&gt;
&lt;li&gt;Cases where the font can&#39;t be found automatically: it prompts you to drag the font file onto the relevant row&lt;/li&gt;
&lt;li&gt;Local system fonts on Chromium-based browsers via the Local Font Access API (permission-gated)&lt;/li&gt;
&lt;li&gt;SVGO optimisation (optional, off by default; SVGO&#39;s &lt;code&gt;inlineStyles&lt;/code&gt; pass removes &lt;code&gt;@font-face&lt;/code&gt; rules if you&#39;re not careful)&lt;/li&gt;
&lt;li&gt;Stripping legacy &lt;code&gt;&amp;lt;font&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;glyph&amp;gt;&lt;/code&gt; blocks that some editors still emit&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;where-it-gets-bumpy&quot; tabindex=&quot;-1&quot;&gt;Where it gets bumpy&lt;a href=&quot;#where-it-gets-bumpy&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the bulk of SVG-with-text use cases (infographics, charts, diagrams, posters, headlines exported from Illustrator or Figma) the workflow just works. Where it gets shakier is anything leaning on OpenType machinery or rich text layout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CORS-blocked fonts&lt;/strong&gt;: a font hosted without permissive headers can&#39;t be fetched from the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variable fonts&lt;/strong&gt;: subsetting variable fonts is finicky in general. If your SVG depends on a custom axis, test the output before shipping.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenType features&lt;/strong&gt;: ligatures, contextual alternates, and stylistic sets sometimes reach for glyphs that don&#39;t appear in the visible text. The Basic Latin baseline catches most cases; obscure feature-driven glyphs may still drop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex shaping scripts&lt;/strong&gt;: Arabic, Devanagari, and similar scripts depend on positional and contextual glyphs. If your SVG uses one, eyeball the output rendering before trusting it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;try-it&quot; tabindex=&quot;-1&quot;&gt;Try it&lt;a href=&quot;#try-it&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;


&lt;figure&gt;
  &lt;img src=&quot;https://www.allaboutken.com/images/blog/svgomg-font-ui.jpg&quot; alt=&quot;Screenshot of SVGOMG-Font&#39;s split-screen comparison slider. Left panel labelled &#39;BEFORE&#39; shows &#39;872 bytes · families: Playwrite DE SAS&#39; and the headline &#39;Thank you for shipping Q3.&#39; rendered in default serif. Right panel labelled &#39;AFTER&#39; shows &#39;55,426 bytes · 1 font face embedded · renders everywhere&#39; and the same headline rendered in connected cursive Playwrite DE SAS.&quot;&gt;
  &lt;figcaption&gt;The split-screen slider in the tool itself. Drag the divider to scrub between the broken fallback and the embedded version, with file sizes and font count surfaced in each panel&#39;s header.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://khawkins98.github.io/svgomg-font/&quot;&gt;SVGOMG-Font is live on GitHub Pages&lt;/a&gt;. Drop your SVG in; if its font references resolve, you&#39;ll get a fixed file back in a few seconds.&lt;/p&gt;
&lt;p&gt;The source is &lt;a href=&quot;https://github.com/khawkins98/svgomg-font&quot;&gt;on GitHub&lt;/a&gt;. Bug reports welcome; font name matching in the wild has a long tail.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Related posts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260216-90s-desktop-paradigm-browser-utilities/&quot;&gt;Your browser utility wants to be a floating palette&lt;/a&gt; (same &amp;quot;do one thing, entirely in the browser&amp;quot; design philosophy)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260225-introducing-pinment/&quot;&gt;Introducing Pinment&lt;/a&gt; (another tool from the same season)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260216-introducing-pdf-a-go-slim/&quot;&gt;PDF-A-go-slim: a browser-based PDF optimizer&lt;/a&gt; (stripping and compressing PDFs, no upload required)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://khawkins98.github.io/Foveacast/&quot;&gt;Foveacast: predicted-attention heatmaps in the browser&lt;/a&gt; (another client-side tool, same do-one-thing philosophy; blog post coming)&lt;/li&gt;
&lt;/ul&gt;

</content>
  </entry>
  <entry>
    <title>Designing analytics infrastructure that measures audience quality, not just traffic</title>
    <link href="https://www.allaboutken.com/work/2026/impact-story-undrr-analytics-infrastructure/"/>
    <published>2026-04-22T00:00:00.000Z</published>
    <updated>2026-04-22T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/work/2026/impact-story-undrr-analytics-infrastructure/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="analytics" />
    <category term="content strategy" />
    <category term="information architecture" />
    <summary type="html">15+ websites, no existing benchmarks, and a fundamental question: how do you know if the right people are finding your content?</summary>
    <content type="html">


&lt;p&gt;&lt;strong&gt;Clarity&lt;/strong&gt; | &lt;strong&gt;Audience Quality&lt;/strong&gt; | &lt;strong&gt;Strategic Alignment&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;kh-text-body--3&quot;&gt;
  &lt;div class=&quot;kh-note-box&quot;&gt;
    &lt;span class=&quot;kh-note-box__label&quot;&gt;Context&lt;/span&gt;
    The United Nations Office for Disaster Risk Reduction (UNDRR) is the UN&amp;#39;s focal point for disaster risk reduction, coordinating global policy and supporting Member States to reduce disaster risk and losses.
  &lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;Traditional web analytics answers &amp;quot;how many people visited?&amp;quot; For a UN organization producing policy guidance, technical resources, and knowledge products, that&#39;s the wrong question. The right question: did the people who need this content actually find it?&lt;/p&gt;
&lt;p&gt;I led the design and implementation of analytics infrastructure across UNDRR&#39;s web ecosystem (15+ domains spanning Drupal sites, Java applications, and static microsites) to answer that. The challenge wasn&#39;t primarily technical. It was conceptual: what does success mean for content where impact happens outside your analytics?&lt;/p&gt;
&lt;h2 id=&quot;what-was-the-actual-problem&quot; tabindex=&quot;-1&quot;&gt;What was the actual problem?&lt;a href=&quot;#what-was-the-actual-problem&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No analytics benchmarks exist for organizations like UNDRR. The widely-cited &amp;quot;16% social traffic&amp;quot; figure comes from fundraising nonprofits running emotional campaigns. For a technical policy organization where audiences search for &amp;quot;Sendai Framework implementation guidance,&amp;quot; that benchmark steers you wrong. I needed to figure out what actually applied, and write down what didn&#39;t.&lt;/p&gt;
&lt;p&gt;The measurement model was broken in a different way. A 50% bounce rate on a terminology definition often means the user found their answer. The same rate on a landing page means something failed. Without classifying content by type, aggregate metrics obscure more than they reveal.&lt;/p&gt;
&lt;p&gt;Then there&#39;s the volume problem. 10,000 visits from random browsers isn&#39;t worth more than 100 visits from government ministries using the content to shape national policy. Traditional analytics gives you geography and device types. It doesn&#39;t tell you whether the right people showed up.&lt;/p&gt;
&lt;p&gt;Some of the most important impact never touches analytics at all. A researcher downloads a dataset and cites it in a paper six months later. A ministry official reads guidance and it shapes a national strategy. Neither event shows up as a conversion.&lt;/p&gt;
&lt;h2 id=&quot;how-did-i-approach-this&quot; tabindex=&quot;-1&quot;&gt;How did I approach this?&lt;a href=&quot;#how-did-i-approach-this&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started with benchmarks — or rather, with finding out they didn&#39;t exist for our context. I synthesized data from M+R Benchmarks, RKD Group studies (2,000+ nonprofits), First Page Sage, and Similarweb, then worked out which findings actually applied. One thing that came up: only 8% of nonprofit websites pass Core Web Vitals on mobile. I documented confidence levels for each benchmark category and noted the gaps: no UN-system analytics data exists anywhere.&lt;/p&gt;
&lt;p&gt;Content-type classification underpins everything else. I established metatag conventions (&lt;code&gt;vf:page-type&lt;/code&gt;) that tag every page by type. The tracking script reads these on page load and sends them to GA4 as a custom dimension, so you can apply different success criteria to different content. Reference pages get different expectations than landing pages, which get different expectations than event pages.&lt;/p&gt;
&lt;p&gt;For audience quality, I built an ASN lookup: a server-side endpoint converts IP addresses to network categories (government, academic, intergovernmental, NGO, commercial) and sends the category to GA4. Not the specific organization, just the type, which keeps it GDPR-friendly while still answering the real question: are the visitors we&#39;re getting the visitors we&#39;re trying to reach?&lt;/p&gt;
&lt;p&gt;I also classified 200+ referrer domain patterns into 14 strategic buckets (UN System, Government, Academic, DRR Community, Traditional Media, AI Chatbots, and others), each carrying a weight reflecting how much that traffic matters to the mission. An &amp;quot;impact score&amp;quot; multiplies sessions by weight. 100 government visits (5×) ranks higher than 400 unclassified visits (0.5×). Raw session counts stop being the main signal.&lt;/p&gt;
&lt;p&gt;Topic-level analysis came from taxonomy metatags (&lt;code&gt;vf:page-terms&lt;/code&gt;) covering hazards, themes, SDGs, and Sendai Framework priorities. The backend aggregates by topic, so you can ask &amp;quot;which hazards drive the most search demand?&amp;quot; rather than &amp;quot;which pages were popular last month?&amp;quot; A lightweight &amp;quot;Was this page helpful?&amp;quot; widget sends clicks to GA4 with page container context; it&#39;s most useful for low-traffic content where engagement metrics are too thin to read.&lt;/p&gt;
&lt;p&gt;The architecture follows a three-layer model. Content layer: Drupal, Java apps, and static microsites all emit the same metatag conventions. Tracking layer: one shared script, one GA4 property, per-domain data streams. Consumption layer: dashboard, browser extension, and Looker Studio, all reading the same source. Adding a new site means implementing metatags. The infrastructure doesn&#39;t change.&lt;/p&gt;
&lt;p&gt;The single-property decision took some defending. Separate GA4 properties per site would have been simpler to manage, but would have fragmented the data. With one property and hostname filtering, the same query answers &amp;quot;how&#39;s PreventionWeb doing?&amp;quot; or &amp;quot;how&#39;s the whole ecosystem doing?&amp;quot; Cross-site user journeys become visible for the first time.&lt;/p&gt;
&lt;h2 id=&quot;what-can-we-now-answer&quot; tabindex=&quot;-1&quot;&gt;What can we now answer?&lt;a href=&quot;#what-can-we-now-answer&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;58% of traffic comes from identifiable institutions: government, academic, UN system, research networks. That answers &amp;quot;are the right people finding policy guidance?&amp;quot; in a way raw page views can&#39;t.&lt;/p&gt;
&lt;p&gt;Topic aggregation surfaces patterns that page-level data hides. Flood content dominates hazard interest; Early Warning leads on themes. That feeds content planning decisions, not just retrospective reporting.&lt;/p&gt;
&lt;p&gt;A terminology page with 40% engagement isn&#39;t &amp;quot;underperforming&amp;quot; — it&#39;s what quick-reference content looks like. Content-type benchmarks mean the dashboard compares each page against the right expectations, not a generic industry average.&lt;/p&gt;
&lt;p&gt;On direct feedback: terminology pages run 78% helpful, publications 65%. Pages that drop below threshold get flagged. Not a perfect signal, but a real one for content where traffic is too low for engagement rates to tell you much.&lt;/p&gt;
&lt;h2 id=&quot;what-did-i-learn&quot; tabindex=&quot;-1&quot;&gt;What did I learn?&lt;a href=&quot;#what-did-i-learn&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I built 18 dashboard screens before asking which ones stakeholders would actually use.&lt;/strong&gt; I should have done the user research first. That sequence mistake cost real time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We also set targets before documenting baselines.&lt;/strong&gt; Now we&#39;re establishing them retroactively. The order should have been: observe, then set expectations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some impact just isn&#39;t measurable through analytics.&lt;/strong&gt; Download clicks don&#39;t tell you whether anyone read the PDF. Policy influence doesn&#39;t generate a GA4 event. I documented these gaps explicitly; better to set that expectation early than have people treat the dashboard as a universal answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I underestimated the research.&lt;/strong&gt; Deciding which benchmarks apply, which don&#39;t, and writing down the reasoning became as useful as the tracking implementation itself. Without it, you&#39;re measuring against whatever number was easiest to find. That number is usually wrong for your context.&lt;/p&gt;


&lt;aside class=&quot;kh-note-box&quot;&gt;
  &lt;span class=&quot;kh-note-box__label&quot;&gt;Related:&lt;/span&gt;
  &lt;a href=&quot;https://www.allaboutken.com/posts/20260115-how-to-measure-impact-when-analytics-lie/&quot;&gt;How to measure impact when analytics lie&lt;/a&gt;, the practitioner&#39;s guide: implementing benchmarks, content-type classification, and audience quality detection in your own stack.
&lt;/aside&gt;

&lt;h2 id=&quot;links-and-resources&quot; tabindex=&quot;-1&quot;&gt;Links and resources&lt;a href=&quot;#links-and-resources&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20251203-measuring-success-beyond-page-view/&quot;&gt;Measuring success beyond the page view&lt;/a&gt;: the philosophy behind this approach&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/posts/20260115-how-to-measure-impact-when-analytics-lie/&quot;&gt;How to measure impact when analytics lie&lt;/a&gt;: implementing benchmarks, audience detection, and content-type classification for your own stack&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allaboutken.com/work/2026/impact-story-undrr-analytics-overlay-mcp/&quot;&gt;UNDRR analytics overlay and MCP bridge&lt;/a&gt;, the next chapter: putting this data where the work happens, and making it queryable by AI agents&lt;/li&gt;
&lt;/ul&gt;

</content>
  </entry>
  <entry>
    <title>Let AI worry about the code, you worry about the work</title>
    <link href="https://www.allaboutken.com/posts/20260420-let-ai-worry-about-the-code/"/>
    <published>2026-04-20T00:00:00.000Z</published>
    <updated>2026-04-20T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/posts/20260420-let-ai-worry-about-the-code/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="AI" />
    <category term="developer tools" />
    <category term="software engineering" />
    <summary type="html">Cultivating LLM productivity requires comfort with the vernacular of code, not the writing of it.</summary>
    <content type="html">
&lt;p&gt;Early in 2026 I was in a hotel room during a team retreat, trying to work through a problem with all the context from our planning conversations still circling in my head. I had been using ChatGPT for coding help, then Cursor with the AI in the IDE sidebar; both useful, both essentially &amp;quot;AI helps me code.&amp;quot; I was the one writing; the model filled in the gaps. What I realized in that hotel room was that holding the context in my head and passing it to the machine one chat turn at a time was not enough. I needed to put it somewhere the agent could read directly.&lt;/p&gt;
&lt;p&gt;So I stopped asking an AI to help me code and started managing a project. I opened a fresh git repository, dropped in a short PRD of what I wanted, technical documents on the architecture of the data it would consume, and a folder of the business context behind it. I pointed Claude Code at the repo, described a bit how to begin, and let it stand up a local server. From there I worked through it the way you work through any project: branch, change, diff, commit, revise. My attention moved from the line and the function up to the project. The implementation work, which had been mine, became the agent&#39;s.&lt;/p&gt;
&lt;p&gt;What changed was not the language model; it was the level of abstraction I was operating at.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The productive pattern&lt;/strong&gt;: a coding agent working on a real project, primed with your business context via skill files, local MCP servers, and data on disk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The literacy that matters&lt;/strong&gt;: reading diffs, driving git, editing configs, judging what the agent produced, not writing code from scratch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The dividing line&lt;/strong&gt;: &amp;quot;do you work on a real project with a coding agent?&amp;quot; Chat windows and app-builders do not clear the bar yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Another thing I started doing in that session: dictating instructions instead of typing them. Speaking is looser and more narrative; when I type, I try to be concise, and concision turns out to be the wrong register for this. LLMs read people well: a messy paragraph of what you actually mean beats a tight sentence that asks the model to infer the rest.&lt;/p&gt;
&lt;aside class=&quot;kh-marginnote&quot;&gt;This is the higher-gear behaviour I described in &lt;a href=&quot;https://www.allaboutken.com/posts/20260405-four-phases-of-ai-adoption/&quot;&gt;&lt;em&gt;Four gears of AI-assisted development&lt;/em&gt;&lt;/a&gt; — orchestration, not hand-feeding.&lt;/aside&gt;
&lt;p&gt;That change in productivity is not from writing more code, and it is not from prompting a chatbot. It is the fusion of the two: a coding agent acting on a real project, with me operating one level of abstraction up from the code itself. A chat thread rewards turn-by-turn completion and quietly fights &amp;quot;step back and tell me what we are actually trying to build&amp;quot;; a project folder does not.&lt;/p&gt;
&lt;p&gt;If you have comfort with the &lt;em&gt;vernacular&lt;/em&gt; of code (reading a diff, trusting a commit, knowing what a repo looks like, noticing which file an MCP server is reaching into and why), the leverage is real and it compounds quickly. You do not need to write the code yourself; the agent is writing most of it now. If you do not have that comfort, most of the efficiency gains happening right now will pass you by, because they live where code lives: in a project folder, behind a git remote, wired to a handful of MCP servers you need to trust.&lt;/p&gt;
&lt;h2 id=&quot;what-this-looks-like-in-practice&quot; tabindex=&quot;-1&quot;&gt;What this looks like in practice&lt;a href=&quot;#what-this-looks-like-in-practice&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At UNDRR, our Google Analytics is, like many places, not generic. Twenty-two hostnames funnel into a property, download events are derived from URL patterns rather than clicks, engagement is tagged by page region, and referrers are bucketed by strategic category. I asked Copilot CLI to encode that business context in a local MCP server, so that a coding agent could reach GA4 already knowing what our data means. (The full write-up is in the &lt;a href=&quot;https://www.allaboutken.com/work/2026/impact-story-undrr-analytics-overlay-mcp/&quot;&gt;UNDRR analytics overlay and MCP bridge&lt;/a&gt; piece.)&lt;/p&gt;
&lt;p&gt;I also keep a local copy of our Drupal content database running on the same virtual machine. When the analytics are ambiguous about what a given URL is, the agent can cross-reference the content itself (taxonomy, body text, publication date) and resolve the question without asking me.&lt;/p&gt;
&lt;p&gt;With that setup I can ask:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Tell me about the last ten news items we published on tropical cyclones and how they performed compared to a year ago.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In months past that investigation was an incredibly tedious cross-tool query: pull a filtered content list from Drupal, map each URL to its analytics row, compute year-on-year deltas by custom event, assemble the results into a table. The agent does all of it in one pass, because each of the pieces is reachable and each is already annotated with the context the agent needs to use them.&lt;/p&gt;
&lt;p&gt;There is no off-the-shelf commercial tool that answers that question, and there probably never will be. The thing that makes the query answerable is the specific marriage of &lt;em&gt;our&lt;/em&gt; content structure, &lt;em&gt;our&lt;/em&gt; analytics configuration, and a coding agent fluent in both. The off-the-shelf option would only ever be one of the three. This is what &lt;a href=&quot;https://www.allaboutken.com/posts/20260330-context-was-always-the-job/&quot;&gt;priming an agent with business context&lt;/a&gt; looks like when it actually pays off.&lt;/p&gt;
&lt;h2 id=&quot;comfort-with-code-not-fluency-in-it&quot; tabindex=&quot;-1&quot;&gt;Comfort with code, not fluency in it&lt;a href=&quot;#comfort-with-code-not-fluency-in-it&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The people who benefit most from this moment are those comfortable &lt;em&gt;around&lt;/em&gt; code, not necessarily those who write a lot of it. The agent is writing most of the JavaScript or Python now. What you need is enough fluency to read what it produced, point it at the right place, trust or push back on a proposed change, and know what is happening on your own machine. In practice that looks like: comfort with a filesystem and a project structure, comfort with git (commits, branches, diffs, PRs), comfort with a terminal, comfort editing a config file, comfort registering an MCP server. You may never stand one up from scratch yourself; you can ask the agent to, read what it did, and decide whether to keep it.&lt;/p&gt;
&lt;p&gt;The floor used to be &amp;quot;can you create this code?&amp;quot; and is now closer to &amp;quot;can you direct what the agent writes?&amp;quot; That is a lower bar than before, and still not zero. The people on the wrong side of it will miss most of the leverage in this cycle, not because they cannot code, but because they will not open the terminal.&lt;/p&gt;
&lt;h2 id=&quot;how-to-get-there&quot; tabindex=&quot;-1&quot;&gt;How to get there&lt;a href=&quot;#how-to-get-there&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you are already comfortable with code, build one project-attached workflow for something you do every week. Often that is just a folder with the right context files (a PRD, a few technical docs, a sample of your data) and a coding agent pointed at it with access to the tools you already use. A skill file or a tiny MCP server can help, but they are one path, not the only one. The first version will underperform the manual process; the second will surprise you.&lt;/p&gt;
&lt;p&gt;If you are not comfortable with these tools yet, the investment is not &amp;quot;learn to code&amp;quot;; it is vocabulary. &lt;a href=&quot;https://docs.github.com/en/get-started/start-your-journey&quot;&gt;GitHub&#39;s introductory docs&lt;/a&gt; walk through commits, branches, and pull requests in plain language; &lt;a href=&quot;https://skills.github.com/&quot;&gt;GitHub Skills&lt;/a&gt; turns the same material into guided exercises. A week of either gives you enough to read what an agent is doing on your behalf.&lt;/p&gt;
&lt;aside class=&quot;kh-marginnote&quot;&gt;A bigger thought I want to come back to: we are in a pre-GUI moment for LLM interfaces, and the text-based ones (terminals, Markdown briefs, git histories) are winning so far — not because text is the final answer, but because text is honest about what is happening. More in a future post.&lt;/aside&gt;
&lt;p&gt;From there, install a coding agent (Claude Code, Cursor, or GitHub Copilot CLI), point it at a real repo of yours or one you forked, and let it work. Watch the diffs. Register one MCP server. Edit one config file. You do not need to produce code yourself for any of this to pay off; you need to be a credible reader, director, and reviewer of what the agent produces.&lt;/p&gt;
&lt;p&gt;If you are already running coding agents against real projects (with or without custom skills or MCP servers), I would like to hear what you have automated and what surprised you. Drop me a note; I am collecting examples for a follow-up.&lt;/p&gt;

</content>
  </entry>
  <entry>
    <title>Putting GA4 where the work happens: an in-browser analytics overlay and its MCP bridge</title>
    <link href="https://www.allaboutken.com/work/2026/impact-story-undrr-analytics-overlay-mcp/"/>
    <published>2026-04-20T00:00:00.000Z</published>
    <updated>2026-04-20T00:00:00.000Z</updated>
    <id>https://www.allaboutken.com/work/2026/impact-story-undrr-analytics-overlay-mcp/</id>
    <author>
      <name>Ken Hawkins</name>
      <email>khawkins98@gmail.com</email>
    </author>
    <category term="analytics" />
    <category term="developer tools" />
    <category term="AI" />
    <summary type="html">Content teams shouldn&#39;t have to leave the page to learn how it performs, and neither should AI agents.</summary>
    <content type="html">


&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt; | &lt;strong&gt;Self-service&lt;/strong&gt; | &lt;strong&gt;Agent-queryable&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;kh-text-body--3&quot;&gt;
  &lt;div class=&quot;kh-note-box&quot;&gt;
    &lt;span class=&quot;kh-note-box__label&quot;&gt;Context&lt;/span&gt;
    The United Nations Office for Disaster Risk Reduction (UNDRR) is the UN&amp;#39;s focal point for disaster risk reduction, coordinating global policy and supporting Member States to reduce disaster risk and losses.
  &lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;Web analytics are most useful alongside the context of the pages you are reasoning about. Using AI agents to help also suffers as humans usually manually hand off data.&lt;/p&gt;
&lt;p&gt;To help myself and the team, I built a Chromium extension that puts GA4 metrics on UNDRR pages as an overlay, and extended it with a local MCP server so humans can have agents query the same data through the Chromium extension&#39;s same authenticated session.&lt;/p&gt;
&lt;p&gt;The overlay and the MCP bridge are the same tool wearing two faces: same auth, same config file, same measurement model. That is why neither had to be rebuilt to serve the other. This is the story of both pieces and why they belong together.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These are the three web pages I am responsible for. Can you tell me what links people are not clicking on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Those sorts of questions used to take an age to answer, but now we have quicker, better and more understandable answers.&lt;/p&gt;
&lt;h2 id=&quot;what-was-the-actual-problem&quot; tabindex=&quot;-1&quot;&gt;What was the actual problem?&lt;a href=&quot;#what-was-the-actual-problem&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Context switching to Looker Studio is where curiosity dies.&lt;/strong&gt; For editors wondering whether the policy page they just updated is getting traction, a five-step dashboard drill is slow enough that most of the time they skip it. The question gets answered only when the quarterly report lands, which is the wrong feedback loop for editorial decisions. We spent more time training editors on the tool than understanding the data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UNDRR&#39;s analytics model is not generic.&lt;/strong&gt; Twenty-two hostnames feed one GA4 property, downloads derive from URL patterns containing &lt;code&gt;/media/&lt;/code&gt;, engagement is tagged by page region (main, header, footer, feedback widget), and referrers are classified into eighteen strategic buckets before query time. Off-the-shelf analytics tools either ignore that model or flatten it.&lt;/p&gt;
&lt;h2 id=&quot;how-did-i-approach-this&quot; tabindex=&quot;-1&quot;&gt;How did I approach this?&lt;a href=&quot;#how-did-i-approach-this&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Overlay first, where the work happens.&lt;/strong&gt; The extension is a Manifest V3 Chromium extension. It authenticates through the Chrome Identity API, so there are no extra credentials. On any UNDRR page it can surface views, sessions, engagement rate, average duration, and a year-over-year comparison with an interactive daily chart. Tracked links and downloads get color-coded badges on the page itself; regions get rollups so editors can see which part of a page is actually doing the work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bridge second, on an auth boundary that already existed.&lt;/strong&gt; The MCP server runs on a &lt;code&gt;localhost&lt;/code&gt; port and talks to an offscreen document inside the extension over a persistent WebSocket. That offscreen document owns the OAuth session, so the MCP server never sees or stores a token. From the agent&#39;s side, fourteen tools are exposed: tactical ones (&lt;code&gt;query_top_content&lt;/code&gt;, &lt;code&gt;query_downloads&lt;/code&gt;, &lt;code&gt;query_traffic_sources&lt;/code&gt;, &lt;code&gt;query_geographic_reach&lt;/code&gt;, &lt;code&gt;query_region_events&lt;/code&gt;), strategic composites (&lt;code&gt;domain_overview&lt;/code&gt;, &lt;code&gt;annual_report_extract&lt;/code&gt;), and a raw GA4 escape hatch (&lt;code&gt;run_raw_ga4_query&lt;/code&gt;) for questions the composites do not anticipate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One source of truth, shared three ways.&lt;/strong&gt; &lt;code&gt;domains.json&lt;/code&gt; holds the twenty-two hostnames, their measurement IDs, and the referrer bucket classifiers. The extension&#39;s content script, its popup, and the MCP server all read the same file, so there is no config drift between what editors see in the overlay and what an agent sees through MCP.&lt;/p&gt;
&lt;h2 id=&quot;what-were-the-outcomes&quot; tabindex=&quot;-1&quot;&gt;What were the outcomes?&lt;a href=&quot;#what-were-the-outcomes&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Context at the point of editing.&lt;/strong&gt; Editors can now see how a page is performing while they are editing it. The question &amp;quot;is this working?&amp;quot; has a three-second answer instead of a five-step Looker Studio drill; editorial decisions close the loop on the same day rather than the next quarter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent-speed analysis.&lt;/strong&gt; &amp;quot;Extract the annual report for 2025&amp;quot; is a single agent turn instead of roughly a forty-click dashboard walk. Parallel GA4 query batching inside &lt;code&gt;annual_report_extract&lt;/code&gt; roughly halves the wall-clock time on multi-section reports, which matters when a report spans a dozen sections.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One credential, not two.&lt;/strong&gt; The agent reuses the overlay&#39;s OAuth session, so nobody is maintaining a second service account or rotating a separate key. For a small team inside a UN organisation, that is the difference between a tool that gets adopted and one that quietly stops being used.&lt;/p&gt;
&lt;h2 id=&quot;why-did-this-work&quot; tabindex=&quot;-1&quot;&gt;Why did this work?&lt;a href=&quot;#why-did-this-work&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A tailored solution made sense because the measurement model is non-generic.&lt;/strong&gt; A general-purpose analytics MCP would expose GA4 as GA4. What editors and analysts actually need is GA4 shaped like UNDRR&#39;s referrer buckets, regions, and content types. Keeping the domain logic in &lt;code&gt;domains.json&lt;/code&gt; rather than in prompts means every agent gets the same context for free.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The MCP was not a rewrite.&lt;/strong&gt; It was a thin wrapper on an auth boundary that already existed. The extension already had an OAuth session, an offscreen document, and a query layer; MCP just made them addressable from outside the browser. Most of the hard decisions were security decisions: single-property lock, sender validation, passive failure modes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The one thing that nearly broke it.&lt;/strong&gt; A single bad Zod schema (valid in v3, rejected in v4) made &lt;code&gt;tools/list&lt;/code&gt; throw at registration, and that error is global: all fourteen tools disappeared from the agent at once. A stdio smoke test calling &lt;code&gt;tools/list&lt;/code&gt; as the first build step now catches this class of failure; without it, the MCP server would have shipped advertising capabilities it could not deliver.&lt;/p&gt;
&lt;h2 id=&quot;whats-next-and-why-this-matters-beyond-undrr&quot; tabindex=&quot;-1&quot;&gt;What&#39;s next, and why this matters beyond UNDRR&lt;a href=&quot;#whats-next-and-why-this-matters-beyond-undrr&quot; class=&quot;kh-anchor&quot; aria-label=&quot;Permalink to this heading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Self-service analytics inside the page is a familiar idea. Agent-queryable analytics that reuses an existing auth session is newer. This extension is one instance of the pattern I described in &lt;a href=&quot;https://www.allaboutken.com/posts/20260420-let-ai-worry-about-the-code/&quot;&gt;&lt;em&gt;Let AI worry about the code, you worry about the work&lt;/em&gt;&lt;/a&gt;: a coding agent on a real project, primed with your business context. The MCP server is the specific shape that worked here; the shape that works for you might be simpler.&lt;/p&gt;

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