<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" 
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Modern Web Development with Chrome</title>
    <link>https://paul.kinlan.me/</link>
    <description>Recent content on Modern Web Development with Chrome</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>paul.kinlan@gmail.com (Paul Kinlan)</managingEditor>
    <webMaster>paul.kinlan@gmail.com (Paul Kinlan)</webMaster>
    <copyright>Paul Kinlan 2020</copyright>
    <lastBuildDate>Sun, 05 Jul 2026 10:00:00 +0000</lastBuildDate>
    
	<atom:link href="https://paul.kinlan.me/index.xml" rel="self" type="application/rss+xml" />
    
    
    
    <item>
      <title>The web the model sees</title>
      <link>https://paul.kinlan.me/the-web-the-model-sees/</link>
      <pubDate>Sun, 05 Jul 2026 10:00:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/the-web-the-model-sees/</guid>
      <description>A run of recent posts on aifoc.us that I did not plan as a series but which turned into one. The thread is that the web a model sees is staler and thinner than the one you ship, and I have mostly been trying to find ways to see it.</description>
      <content:encoded> <![CDATA[<p>I have written a run of posts over on <a href="https://aifoc.us/">aifoc.us</a> that I did not plan as a series, but they kinda just plopped out.</p>
<p>I'm very interested in what the models have visibilty into and the differences between the models, and then by extension what it means for us as web developers. I think we're not in the best position and a lot of the data in the models is supprisingly stale. In <a href="https://aifoc.us/please-mind-the-model-gap/">please mind the model gap</a> I tried to measure how far behind a model actually is:</p>
<blockquote>
<p>[...] the platform did not stop moving the day the training data was frozen.</p>
</blockquote>
<p>The <a href="https://aifoc.us/model-gap.html">interactive timeline in that post</a> plots each model's cutoff against Chrome releases, and the gap is bigger than most people expect. I don't really know why we don't talk about this more. If I was a library author, or a <a href="https://aifoc.us/dead-framework-theory">new framework author</a>, or heck, I just updated and released a new version, then I would be doing all I could possible to make sure the models had access to it (and search isn't always the answer imo)</p>
<p>I did also try to check whether the &quot;model half-life&quot; everyone keeps citing is even a real thing, and mostly <a href="https://aifoc.us/model-half-life/">talked myself out of it</a>:</p>
<blockquote>
<p>On reflection, model halflife really doesn't make much sense and it's just a bit of a buzzword to mean that models now ship faster.</p>
</blockquote>
<p>What surprised me was the lack of data &quot;in the model&quot;. I had a lot of fun and I spent a lot of money and a long time on <a href="https://aifoc.us/influencing-model-output-with-urls/">does a url in a prompt steer an llm's output</a>, testing whether a URL sitting in a prompt changes the answer, and the bit that stuck with me was not about URLs at all:</p>
<blockquote>
<p>If your site relies on JavaScript to load its content, that content is very likely not in a model (you might consider that a feature).</p>
</blockquote>
<p>You can watch it happen in <a href="https://paulkinlan.github.io/url-influence/results/dashboard.html">the results</a>: the models can't (obvjously) can't recall the data they don't have when given an identifier (a url) that's not in the model. They then recover when you hand them the actual page (kinda obvious). When the content <em>is</em> memorised, the URL seems to clearly tilt the output just by being there towards the content that behind that URL, which if this really really is true, I think is something we can take advantage of instead of stuffing our prompts with context, the mere presence of that URL will influence the output to align with what's behind that page - this could be a big win for skills and well established best practices.</p>
<p>During the proccess of creating the post I found that there was 0 recall for ChromeStatus.com and I thought it was a pointer to my idea failing and it turns out that ChromeStatus.com is JS rendered and the content wasn't in things like <code>CommonCrawl</code> and by extension depending on what the indexer's did, likely a pointer to the data not being in the model.</p>
<p>That left then left me with a question I couldn't easily answer at the time: when one of the many bots hitting those URLs loads a page, what does it actually <em>do</em>? Fetch the HTML and stop? Parse the CSS? Run the JavaScript, or just download the <code>.js</code> and move on? So I built <a href="https://uatracer.com">ua-tracer</a> to watch, and <a href="https://paul.kinlan.me/ua-tracer/">wrote it up here</a></p>
<p>The early data already broke an assumption I did not know I was holding, that &quot;an AI read my page&quot; means one thing. It does not. <a href="https://uatracer.com/trace/w0qUMmns">GPTBot parses your CSS but runs no JavaScript</a>, <a href="https://uatracer.com/trace/GS6aE5u1">ClaudeBot stops at the CSS file</a>, and of the OpenAI agents only <a href="https://uatracer.com/trace/nrrxKcDC">OAI-SearchBot runs the script</a>. Point a crawler at <a href="https://uatracer.com">uatracer.com</a> and you can see what yours does.</p>
<p>Finally, my last post was something I hope was a bit more constructive and shows how web development might change with new tooling. In <a href="https://aifoc.us/how-might-a-modern-lighthouse-work-with-llms/">how might a modern lighthouse work with large language models</a> I built an audit where the model does the judging rather than a pile of hard-coded checks, and one of the things it looks at is how much of the page exists before any JavaScript runs. I pointed it at one of my own sites (<a href="https://iwanttouse.com">iwanttouse.com</a>):</p>
<blockquote>
<p>Coverage was 6 percent, that is, only 6 percent of the words a browser eventually renders were in the HTML before any JavaScript ran.</p>
</blockquote>
<p>I knew at the time that I was building a JS-powered site, but it was nice to see the LLM pick this up based on the descriptive tests and then offer up a solution for how to fix it.</p>
<p>The <a href="https://aifoc.us/web-uplift-playground.html">live playground</a> runs the whole find-fix-verify loop on a set of deliberately broken pages, and there is a <a href="https://aifoc.us/web-uplift-report.html">full report</a> with the  evidence it gathered from the example site if you want to see the shape of the output.</p>
<blockquote>
<p>I love that this is a loop and not a tool.</p>
</blockquote>
<p>I mean... I wrote that bit thinking I was clever, but it's also a tool: <code>npx web-uplift@latest audit https://paul.kinlan.me/</code></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>ua-tracer: what does a user agent actually do?</title>
      <link>https://paul.kinlan.me/ua-tracer/</link>
      <pubDate>Thu, 02 Jul 2026 10:00:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/ua-tracer/</guid>
      <description>ua-tracer is a tiny site that attempts to show you what a user agent fetches, follows, and executes when it loads a page. I built it because we have almost no visibility into what bots and AI agents actually see when they crawl the web.</description>
      <content:encoded> <![CDATA[<p>The work that led to this is a research project that I will soon publish on <a href="https://aifoc.us">aifoc.us</a>, where I am analysing if the presence of a URL in a prompt influences the output based on the latent &quot;knowledge&quot; about that URL in the model. While doing this project I needed to test a heap of URLs and see if their data was in the model or not and I hit on a heap of problems. The more time I spent in that data, the more I realised I couldn't answer a basic question about a large share of the traffic fetching those URLs: when one of the many agents, indexers, and scrapers loads a page, what does it actually do? Download the HTML and stop? Parse the CSS? Follow the font linked from inside that CSS? Can they run the JavaScript, or just fetch the <code>.js</code> file and move on?</p>
<p>So I built <a href="https://uatracer.com">ua-tracer</a> to answer it, and it's a tool that I think will be useful for many web developers trying to understand what any browser or bot does when it accesses your site.</p>
<p><img src="/images/ua-tracer-homepage.png" alt="ua-tracer homepage showing a freshly minted trace"></p>
<p>Click through any row in that list and you land on the trace detail, a request-by-request account of what that one user agent did:</p>
<p><img src="/images/ua-tracer-trace-pvvoocMX.png" alt="A real trace (pvvoocMX) showing what a visiting agent fetched and executed"></p>
<h2 id="how-it-works">How it works</h2>
<p>Every time a user agent loads <code>https://uatracer.com/</code>, the site mints a unique <strong>trace id</strong> <em>and</em> a per-request secret, and renders a page whose every asset (stylesheet, script, images, font, manifest) carries both in its path. The secret is private to that one page load, so only the agent that received the HTML can fetch the probe assets: no guessing, no replay by anyone else.</p>
<pre tabindex="0"><code>/r/{id}/{secret}/style.css     the real stylesheet
/r/{id}/{secret}/main.js       the real script
/r/{id}/{secret}/photo.png     a real PNG
/r/{id}/{secret}/font.woff2    a real woff2 font
</code></pre><p>Because the id is unique per page load, <strong>every</strong> later asset request can be tied back to the exact homepage hit and the user agent that made it. I also added other probes that come what those assets <em>themselves</em> reference (paths below drop the <code>{secret}</code> segment for readability):</p>
<table>
<thead>
<tr>
<th>Probe</th>
<th>Referenced from</th>
<th>Hitting it proves…</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/r/{id}/{secret}/css-bg.png</code></td>
<td>a <code>background-image:</code> inside <code>style.css</code></td>
<td>the UA parsed the CSS and followed a URL inside it</td>
</tr>
<tr>
<td><code>/r/{id}/{secret}/css-font.woff2</code></td>
<td>an <code>@font-face { src: }</code> inside <code>style.css</code></td>
<td>the UA resolved a CSS font source</td>
</tr>
<tr>
<td><code>/r/{id}/{secret}/manifest-icon.png</code></td>
<td><code>icons[].src</code> inside <code>manifest.json</code></td>
<td>the UA <strong>parsed the manifest</strong> and followed an icon</td>
</tr>
<tr>
<td><code>/r/{id}/{secret}/js-ran.gif</code></td>
<td><code>new Image().src = …</code> in <code>main.js</code>, at runtime</td>
<td>the UA <strong>executed classic JS</strong></td>
</tr>
<tr>
<td><code>/r/{id}/{secret}/module-ran.gif</code></td>
<td>a runtime beacon in an ES module</td>
<td>the UA <strong>executed an ES module</strong></td>
</tr>
<tr>
<td><code>/r/{id}/{secret}/timing</code></td>
<td>a <code>POST</code> of <code>performance.getEntriesByType('resource')</code></td>
<td>a real engine ran and produced a client-side waterfall</td>
</tr>
</tbody>
</table>
<p>A plain downloader (think <code>curl</code>) nomrally just fetches the HTML and stops. A CSS-aware fetcher additionally hits <code>css-bg.png</code> and <code>css-font.woff2</code>. A UA that parses the manifest reaches <code>manifest-icon.png</code> etc. Social unfurlers (Twitterbot, Discordbot etc) fetch the social-card image. Finally, only a user agent that runs JavaScript will ever touch <code>js-ran.gif</code> or post to <code>/timing</code>.</p>
<h2 id="what-a-trace-looks-like">What a trace looks like</h2>
<p><img src="/images/ua-tracer-trace-detail.png" alt="A trace detail page showing what a single user agent fetched and executed"></p>
<p>Open any trace and you get a request-by-request account of how far the agent went: which asset types it fetched, whether it followed the CSS-linked resources, whether it executed JavaScript, and the client-side resource-timing waterfall it posted back if it did.</p>
<p>This particular trace is synthetic: I generated it by pointing a made-up user agent at the site and walking it through the assets by hand:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>curl -A <span style="color:#e6db74">&#34;Mozilla/5.0 (compatible; DemoBot/1.0; +https://example.com)&#34;</span> https://uatracer.com/
</span></span><span style="display:flex;"><span><span style="color:#75715e"># then fetch the trace-scoped assets it references:</span>
</span></span><span style="display:flex;"><span>curl https://uatracer.com/r/<span style="color:#f92672">{</span>id<span style="color:#f92672">}</span>/<span style="color:#f92672">{</span>secret<span style="color:#f92672">}</span>/style.css
</span></span><span style="display:flex;"><span>curl https://uatracer.com/r/<span style="color:#f92672">{</span>id<span style="color:#f92672">}</span>/<span style="color:#f92672">{</span>secret<span style="color:#f92672">}</span>/js-ran.gif   <span style="color:#75715e"># the JS-execution beacon</span>
</span></span></code></pre></div><p>The trace detail is intentionally public: share <code>/trace/{id}</code> as a link and anyone can read the result, which makes it easy to pass a finding along (&quot;look, this agent doesn't run JS&quot;).</p>
<h2 id="some-early-analysis-on-what-the-bots-actually-do">Some early analysis on what the bots actually do</h2>
<p>This site hasn't been live that long so it's not got heaps of data yet (hence this post to try and raise some awareness), but I've got some insights that I think are interesting.</p>
<p><strong>One caveat first: a User-Agent string is trivially spoofable.</strong> The tool also checks each request's source IP against the CIDR ranges its operator publishes. Every major crawler operator now publishes a list you can match against: <a href="https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot">Google</a>, <a href="https://platform.openai.com/docs/bots/">OpenAI's GPTBot and OAI-SearchBot</a>, <a href="https://www.bing.com/toolbox/bingbot.json">Bing</a>, and <a href="https://support.claude.com/en/articles/8896518">Anthropic's ClaudeBot</a> (<a href="https://claude.com/crawling/bots.json">IP list</a>). So a &quot;Googlebot&quot; or &quot;ClaudeBot&quot; trace is either <em>verified</em> (the IP is in the operator's published range) or flagged as likely spoofed. The one exception in the data so far is OpenAI's <a href="https://platform.openai.com/docs/bots/">ChatGPT-User</a>, which is triggered by a person rather than run on a schedule and publishes no fixed ranges, so its User-Agent alone can't be confirmed.</p>
<p><strong>Bots from the same company do not behave alike.</strong> OpenAI seems to run at least three agents against the site (all IP-verifiable), and each does something different:</p>
<ul>
<li><strong>GPTBot</strong> (<code>+openai.com/gptbot</code>) fetches every directly-referenced asset and parses the stylesheet, following the <code>background-image</code>, the <code>@font-face</code> source, and the <code>@import</code>. It does not run JavaScript.</li>
<li><strong>OAI-SearchBot</strong> (<code>+openai.com/searchbot</code>) presents a full Mac Chrome User-Agent and runs JavaScript: it executed both the classic script and the ES module, and followed the CSS background image. Of the OpenAI agents, this one is browser-grade.</li>
<li><strong>ChatGPT-User</strong> (<code>+openai.com</code>) fetches the page and its assets but runs no JavaScript and parses no CSS.</li>
</ul>
<p>Anthropic's <strong>ClaudeBot</strong> (<code>+claudebot@anthropic.com</code>) fetches the full asset set like GPTBot, but stops at the CSS file: it never reaches the <code>background-image</code> or <code>@font-face</code> inside it, and it does not run JavaScript. So &quot;an AI is reading my page&quot; breaks three different ways depending on whether that AI is GPTBot, OAI-SearchBot, or ClaudeBot. (<a href="https://uatracer.com/traces?ua=ClaudeBot">ClaudeBot traces</a> · <a href="https://uatracer.com/traces?ua=GPTBot">GPTBot traces</a> · <a href="https://uatracer.com/traces?ua=bot">all bots</a>)</p>
<p><strong>Googlebot is documented to render JavaScript in a second pass</strong> (<a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics">Google's JavaScript SEO docs</a> describe crawl → render queue → a headless Chromium executing the script later). I have not caught that render pass in the traces yet; what I have seen is the fetch-only first stage. So &quot;Googlebot doesn't run JS&quot; would be the wrong conclusion to draw, and the tool is set up to record the second stage if and when it arrives. The same applies to <strong>Applebot</strong> (<a href="http://www.apple.com/go/applebot">Apple's docs</a> say it &quot;may render the content of your website within a browser&quot;); the seven Applebot traces I have are IP-verified real but fetch-only so far.</p>
<p><strong>Some bots announce their intent; most don't.</strong> <code>DomainArrivalsBot/0.1</code> identifies itself as either <code>(+homepage-extract; research)</code> or <code>(+homepage-only; research)</code>, telling you in the UA string whether it takes the whole page or just the root. That honesty is rare. Most agents either masquerade as browsers with full Mozilla/AppleWebKit strings (OAI-SearchBot above is a textbook case) or skip the courtesy entirely: <code>okhttp/5.3.0</code> and <code>Go-http-client/1.1</code> between them account for dozens of visits, and both are raw HTTP libraries with no name.</p>
<p>This is the data I wanted for aifoc.us and couldn't get from any log. The bots reading the web are not one thing; they are a population, and the only way to know what a given one does is to watch it. We can measure page views to the nearest second; we still can't tell whether the thing reading the page ran the code on it.</p>
<h2 id="try-it">Try it</h2>
<p>The site is at <a href="https://uatracer.com">uatracer.com</a>. Open it in a browser to mint your own trace, or point a crawler at it. The <a href="https://github.com/PaulKinlan/ua-tracer">source is on GitHub</a>: a single Deno file and a Deno KV database, nothing more. (note - I've used an agent to build this out)</p>
<p>I'd love some feedback on this and if you have any other tests that you would like me to add.</p>
<h2 id="sources-bot-ip-ranges-and-what-each-operator-says">Sources: bot IP ranges and what each operator says</h2>
<ul>
<li><strong>Google</strong> publishes its crawler IP ranges (<a href="https://developers.google.com/static/crawling/ipranges/common-crawlers.json"><code>common-crawlers.json</code></a>) and recommends a reverse-DNS check: resolve the IP to a <code>googlebot.com</code> hostname, then forward-resolve it back to the same IP (<a href="https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot">Verify Googlebot</a>). Googlebot also runs JavaScript in a separate render pass using an evergreen Chromium (<a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics">JavaScript SEO basics</a>).</li>
<li><strong>OpenAI</strong> publishes ranges for GPTBot (<a href="https://openai.com/gptbot.json"><code>gptbot.json</code></a>) and OAI-SearchBot (<a href="https://openai.com/searchbot.json"><code>searchbot.json</code></a>); overview at <a href="https://platform.openai.com/docs/bots/">platform.openai.com/docs/bots</a>. ChatGPT-User is triggered by a person and has no fixed ranges.</li>
<li><strong>Bing</strong> publishes <a href="https://www.bing.com/toolbox/bingbot.json"><code>bingbot.json</code></a> and a <a href="https://www.bing.com/webmasters/help/how-to-verify-bingbot-3b1e6b56">verification guide</a> using reverse-DNS to a <code>search.msn.com</code> hostname.</li>
<li><strong>Apple</strong> publishes Applebot CIDRs at <a href="https://search.developer.apple.com/applebot.json"><code>search.developer.apple.com/applebot.json</code></a> and a reverse-DNS check against <code>*.applebot.apple.com</code> (<a href="http://www.apple.com/go/applebot">About Applebot</a>). Apple notes Applebot may render pages in a browser, like Googlebot. (Apple also runs <code>Applebot-Extended</code>, which doesn't crawl but controls use of crawled data for model training.)</li>
<li><strong>Anthropic</strong> publishes its crawler IPs at <a href="https://claude.com/crawling/bots.json"><code>claude.com/crawling/bots.json</code></a>, covering ClaudeBot, Claude-User and Claude-SearchBot (<a href="https://support.claude.com/en/articles/8896518">help article</a>). Anthropic notes that IP-based blocking &quot;may not work correctly or persistently guarantee an opt-out&quot;, but the list is sufficient to confirm a request genuinely originates from Anthropic.</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Where my head is at with LLMs and the web</title>
      <link>https://paul.kinlan.me/where-my-head-is-at-with-llms-and-the-web/</link>
      <pubDate>Sun, 07 Jun 2026 20:17:06 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/where-my-head-is-at-with-llms-and-the-web/</guid>
      <description>Where my head is at with LLMs and the future of the web... Quite focused actually.</description>
      <content:encoded> <![CDATA[<p>I have been writing a lot over at <a href="https://aifoc.us/">aifoc.us</a> for the past year (actually, my last post on this site was a year and a day ago), and I've spent a lot of time exploring the risks and opportunities for the web, and the challenges that I think we need to adapt to as an ecosystem.</p>
<p>I kept it distinct from this site because I know that quite a few web developers who follow my content are not fond (to put it mildly) of LLMs and the AI industrial complex, and I felt it would be good to split the two.</p>
<p>I will keep publishing my posts on the other site, but I thought it might be good to try and pull together the 30-odd posts that I've created and try and explain where my head is at and provide some colour on how I got there.</p>
<p>Before I dive too deep, I think we are potentially in a golden age for the web, with a massive influx of people having major barriers to entry removed. Barriers that once were the reason why people preferred Instagram and similar services are being lowered to the point where anyone can build on our platform (and I'm seeing lots more people building).</p>
<p>Anyway, here we go:</p>
<h2 id="models-inherit-a-stale-web-and-they-set-us-back-a-year">Models inherit a stale web, and they set us back a year</h2>
<p>The thing I keep coming back to is that the models we now write code with learned from a web that is already old. I made <a href="https://aifoc.us/the-model-gap/">the model gap</a> to show this concretely: measured in Chrome releases (I know, I know, the web is far broader than just Chrome, but also Chrome has easy data to access on chromestatus.com), even the freshest model is several versions behind, and most are ten to twenty behind. The &quot;knowledge&quot; cutoff is a serious issue for the web platform, and the ecosystem of libraries and tools that are being launched but are not easily available to these models is massively gaining traction (Claude Code).</p>
<p>That connects to <a href="https://aifoc.us/model-half-life/">model half-life</a>, where I looked at how quickly models are superseded, and to <a href="https://aifoc.us/dead-framework-theory/">dead framework theory</a>: if a framework stops appearing in fresh training data, the models stop reaching for it, and the framework quietly dies regardless of its merits. I wrote this thesis at least 6 months ago, and I think I've been proven correct (which is why we built Modern Web Guidance). The flip side, though, is that I've found guided output getting better than what people create (I think auto-research loops to optimize web performance, as an example, will massively raise the bar for the quality of the web people experience).</p>
<p>My uncomfortable conclusion across all of these is that the web inside the model is the web that developers actually inherit, and right now it is a year out of date and getting staler with every release.</p>
<h2 id="we-can-discover-functionality-and-link-to-it">We can discover functionality and link to it</h2>
<p>I have been trying to give the web a federated way to link to functionality for over a decade. I wrote <a href="https://paul.kinlan.me/what-happened-to-web-intents/">what happened to web intents</a> and later <a href="https://paul.kinlan.me/reinventing-web-intents/">reinventing web intents</a> about the first attempt and its failure. The idea never left me; ask anyone who knows me, I never shut up about it.</p>
<p>WebMCP is the thing that finally makes it plausible. I argued the case in <a href="https://aifoc.us/webmcp-is-the-new-web-intents/">webmcp is the new web intents ... maybe</a>, and then, once I had actually built a relay that discovers and reuses a page's tools across sites, I dropped the maybe in <a href="https://aifoc.us/i-think-ive-got-it-web-intents/">i think i've got it</a>. This sits on top of older thinking about why the link is the web's most underrated primitive (<a href="https://aifoc.us/a-link-is-all-you-need/">a link is all you need</a>).</p>
<p>I want to explore more about what an addressable unit of the web should look like for agents, building on <a href="https://paul.kinlan.me/slice-the-web/">SLICE</a>, and how it might evolve. Naturally, there is a lot of worry about the I in SLICE that I want to explore a little bit more.</p>
<h2 id="the-browser-is-an-amazing-sandbox-and-other-platforms-are-racing-to-what-we-have-already">The browser is an amazing sandbox and other platforms are racing to what we have already</h2>
<p>If a program is mostly a prompt, then the interesting question is where you run it safely. I think the browser is the answer, which is the argument in <a href="https://aifoc.us/the-browser-is-the-sandbox/">the browser is the sandbox</a>. That follows from <a href="https://aifoc.us/prompt-is-the-program/">the prompt is the program</a>, where the entire application is a Markdown file, and <a href="https://aifoc.us/shipping-a-prompt/">shipping a prompt</a>, where the portability of a prompt becomes the distribution model.</p>
<p>I have been poking at the edges of this too: <a href="https://aifoc.us/building-a-claw-in-the-browser/">building a claw in the browser</a> is a genuinely dangerous experiment in how much access an in-page agent can get.</p>
<p>I really think we should be exploring this space a lot more, making some small changes to the platform (heh, Safari doesn't have CSP3) and really pushing on what every super-app platform doesn't yet have that can really differentiate the web.</p>
<h2 id="agent-loops-and-what-they-actually-enable">Agent loops, and what they actually enable</h2>
<p>Once you have written a few agents, you see that we have a new pattern for software (that people call agent loops). It is slower and vastly more expensive, but it is also opening up a world where it's easier and easier for people to instruct their machines and get accurate results. It's rather incredible, and it reminds me of how much more approachable HTML was when I started.</p>
<p>I built my own provider-agnostic loop, wrote it up in <a href="https://aifoc.us/agent-do-my-agent-loop/">agent-do</a>, and it enables me to do a <em>lot</em> of experimentation, for example:</p>
<ul>
<li>a whole small company replaced by agents in a container (<a href="https://aifoc.us/business-in-a-box/">business in a box</a>)</li>
<li><a href="https://aifoc.us/building-a-claw-in-the-browser/">building a claw in the browser</a></li>
<li><a href="https://aifoc.us/prompt-is-the-program/">the prompt is the program</a>, where the entire application is a Markdown file</li>
<li>and <a href="https://aifoc.us/shipping-a-prompt/">shipping a prompt</a></li>
</ul>
<p>I know I linked to these above, but I think the browser sandbox and agent loops are going to change a lot about how web experiences could be built in the future. I don't think we are there yet, but I can imagine a world where entire websites and services are higher-level instructions and design guidance.</p>
<h2 id="development-is-changing-quickly">Development is changing quickly</h2>
<p>I really don't know what else to say about this: the sheer volume of machine-authored work already landing, which I measured in <a href="https://aifoc.us/damn-claude-thats-a-lot-of-commits/">damn claude, that's a lot of commits</a>, is astounding, and it could lead to strange new economics of paying for tokens instead of people (<a href="https://aifoc.us/the-token-salary/">the token salary</a>) and how we charge for services (<a href="https://aifoc.us/token-slinging/">token slinging</a>).</p>
<p>I'm personally fully on board with these tools, and I think I've become quite proficient. While I do encourage people to understand how the platform works and how the browser works, I don't think we are going back at all. In fact, everything will accelerate now that the technology and tools exist.</p>
<h2 id="generated-content-and-content-negotiation">Generated content and content negotiation</h2>
<p>This is the thread I find most exciting for the web specifically. If a page can be generated on demand, the old idea of <a href="https://aifoc.us/hyper-content-negotiation/">content negotiation</a> gets a second life: the server does not pick from a few fixed representations, it generates the one you asked for. I keep using <a href="https://aifoc.us/if-notebooklm-was-a-web-browser/">NotebookLM as the proof</a> that a tool which generates a custom view of a corpus is already a kind of browser.</p>
<p>I'm personally kind of shocked there isn't more experimentation about how the future of sites and browsers might work. As tools that generate content get higher quality and quicker, they are going to open up new areas of the platform:</p>
<ul>
<li>If we can <a href="https://aifoc.us/interception/">intercept</a> requests to synthesise responses, what does it enable?</li>
<li>If we take <a href="https://aifoc.us/hypermedia/">hypermedia</a> to the extreme and enable any content to be turned into experiences that people prefer, what will happen? I know a lot of folks who learn via manga; why shouldn't they be able to consume content in their preferred format?</li>
<li>I think we now have the Memex, so what happens to the <a href="https://aifoc.us/whither-cms/">CMS</a> when content is generated (hint: they can adapt), and how can markup change to be more expressive (<a href="https://aifoc.us/headless-stopgap/">Headless stopgap</a>)?</li>
</ul>
<p>There's going to be even more change to the web as the <a href="https://aifoc.us/latency/">latency</a> of models falls, making generating UI and new content modalities on the fly viable (and I think sites should lean into generating content in the form their users prefer).</p>
<p>Maybe we can also get back to <a href="https://aifoc.us/ai-powered-site-mashups/">site mashups</a>, and stave off the threat of <a href="https://aifoc.us/super-apps/">super-apps</a> (btw, this is a huge risk.)</p>
<h2 id="how-browsers-might-adapt">How browsers might adapt</h2>
<p>First, <a href="https://aifoc.us/super-apps/">super-apps</a> are coming, and they hint at a world where you barely open a browser at all. It might be a little way off, but I would love browser vendors to work out how the web will remain a first-class citizen if this happens. That's why I think WebMCP is critical, as is exploring what is listed below.</p>
<p><a href="https://aifoc.us/elements/">Elements</a>: web components are a quietly perfect packaging format for agent-generated UI, and almost nobody frames them that way.</p>
<p>Can we bring in primitives and interactions that you don't get on other platforms? I'd love for us to explore how the link might evolve. I did this in <a href="https://aifoc.us/embedding/">embedding</a>, which is really about transclusion: pulling live functionality from one site into another, the E in SLICE, and an old web dream that agents might finally make real.</p>
<p>I built a lot of demos to try and show this in action:</p>
<blockquote>
<ul>
<li>
<p><a href="https://github.com/PaulKinlan/hyperlink">Hyperlink Experiment</a>: There is a lot in here.... I struggle with the hubris the industry has around the link. I set out to experiment with the concept of linking on the web and I really want to encourage people to really push the boundaries of what a link is and, if possible, how technologies like LLMs and image recognition models change how the web fundamentally works.</p>
</li>
<li>
<p><a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/merge">Merge</a> - <a href="https://chromewebstore.google.com/detail/merge-link/ffpcdfloldhbeielaoiblgalmpkalnjo">Extension</a> - One of the things I love about the web is that you don't really know what is on the other side of a link. One of the things I dislike is that you don't know what is on the other side of a link. I also wonder what the intent of a link is.</p>
</li>
<li>
<p><a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/memex-join">Trails</a> - <a href="https://chromewebstore.google.com/detail/trails/cmhofadlaokelmccnocbnojdbdnfjhga">Extension</a> - This uses no LLM technology, but I built it with an LLM. Vannevar Bush's &quot;user links&quot; let you create your own links on pages and have them be permanent.</p>
</li>
<li>
<p><a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/summary">Summary</a> - Hover over a link and get a summary of what is on the linked page before you click it.</p>
</li>
<li>
<p><a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/stretchtext">Stretch Text</a> - Taking Ted Nelson's idea and trying to make it usable. Select some text and zoom in (expand) or zoom out (summarize). After reading Ted Nelson's ideas on this, it was up to the author to offer this, and knowing how people publish on the web, people just aren't going to do this. LLMs make it possible to add extra context or summarize it. I didn't really test this too much in the end because I wanted to get Merging and Trails working.</p>
</li>
<li>
<p>I made a lot of other experiments, such as linking into images with descriptions, and also making it easy to do <a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/image-links/src">image maps again by using English (using the Segment Anything model)</a> instead of points. <a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/audio-link">Link to audio content via timestamps with a Whisper-like model.</a> <a href="https://github.com/PaulKinlan/hyperlink/tree/main/packages/ui-links">Pull in the UI of a linked element</a> into the current page, taking the Merge experiment one step further.</p>
</li>
</ul>
</blockquote>
<p>If browser vendors choose, they could solve interop issues once and for all. In <a href="https://aifoc.us/how-might-a-browser-be-developed/">how might a browser be developed</a>, I explore how a browser might be developed to the point where the major points of interop difference are fundamental issues of where the browser vendor thinks the web should go. I also go a little further out into some sci-fi.</p>
<h2 id="the-end">The End</h2>
<p>That's roughly where I am. There's still lots I will be exploring.</p>
<p>I am very excited about the future because I think we could continue to see millions more people start publishing on the web, the web getting higher-quality experiences, and waves of new innovation into what the web could be.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>&lt;generate-html&gt; Web Component</title>
      <link>https://paul.kinlan.me/projects/generate-html-element/</link>
      <pubDate>Sat, 27 Dec 2025 12:00:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/generate-html-element/</guid>
      <description>I&#39;ve created the &lt;code&gt;&amp;lt;generate-html&amp;gt;&lt;/code&gt; Web Component, an LLM-powered tool that generates and renders interactive HTML or SVG images securely using Google Gemini or Chrome&#39;s built-in AI. It features a &amp;quot;Double Iframe&amp;quot; architecture for security, supports multiple providers, and has zero framework dependencies. It also allows for template context to guide LLM output.</description>
      <content:encoded> <![CDATA[<p>The <a href="https://github.com/PaulKinlan/generate-html-element/"><code>&lt;generate-html&gt;</code></a> Web Component is a LLM-powered custom element that generates and renders interactive HTML or SVG images on the fly using Google Gemini or Chrome's built-in AI.</p>
<p>It uses a double iframe architecture to reduce the ability of any generated code to access your API keys or host page data. The outer iframe exposes a limited API to the host page it mediates all communication with the inner iframe.</p>
<p>Both iframes are sandboxed and both iframes are created via blob URLs so their origin's are <code>null</code>. This means that the inner iframe cannot access the host page or any of the host page or the outer iframe. The outer iframe can't directly access the DOM of the inner iframe other than changing the <code>href</code> source.</p>
<p>If I was to summarise the features of this component I would say it supports:</p>
<ul>
<li><strong>AI-Generated Content:</strong> Turns text prompts into interactive web apps (calculators, games) or SVG images.</li>
<li><strong>Sandbox:</strong> Uses a &quot;Double iframe&quot; architecture to ensure generated code cannot access your API keys or host page data.</li>
<li><strong>Multi-Provider:</strong> Supports <strong>Google Gemini</strong> (via API Key) and <strong>Chrome Built-in AI</strong> (experimental <code>window.LargeLanguageModel</code>).</li>
<li><strong>Vanilla JS:</strong> Zero framework dependencies. Built with standard Web Components.</li>
</ul>
<h2 id="usage">Usage</h2>
<p>You can install the component directly via npm:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install @paulkinlan/generate-html
</span></span></code></pre></div><p>Then, import the component script and use the tag in your HTML:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;module&#34;</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/src/generate-html.js&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">generate-html</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Create a snake game&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api-key</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;YOUR_GEMINI_API_KEY&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;gemini-2.5-flash-latest&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">provider</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;gemini&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;html&#34;</span>
</span></span><span style="display:flex;"><span>&gt;&lt;/<span style="color:#f92672">generate-html</span>&gt;
</span></span></code></pre></div><p>The demo can be accessed here: <a href="https://generate-html-element.paulkinlan-ea.deno.net/">https://generate-html-element.paulkinlan-ea.deno.net/</a></p>
<p>For more details, check out the <a href="https://github.com/PaulKinlan/generate-html-element/blob/main/README.md">GitHub repository</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Deep Research Email</title>
      <link>https://paul.kinlan.me/projects/deep-research-email/</link>
      <pubDate>Sat, 27 Dec 2025 12:00:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/deep-research-email/</guid>
      <description>I&#39;ve been exploring how to integrate advanced AI agents into my daily workflow, and email remains the most universal interface for asynchronous tasks.
I built a &amp;quot;Deep Research&amp;quot; agent using Val Town that allows me to trigger complex research tasks simply by sending an email. It uses Google&#39;s Deep Research model to perform the heavy lifting.
It is pretty simple to get started, you just need to send an email.</description>
      <content:encoded> <![CDATA[<p>I've been exploring how to integrate advanced AI agents into my daily workflow, and email remains the most universal interface for asynchronous tasks.</p>
<p>I built a &quot;Deep Research&quot; agent using <a href="https://val.town">Val Town</a> that allows me to trigger complex research tasks simply by sending an email. It uses Google's Deep Research model to perform the heavy lifting.</p>
<p>It is pretty simple to get started, you just need to send an email.</p>
<ol>
<li><strong>Send an email</strong> to: <code>deep-research@valtown.email</code></li>
<li><strong>Subject</strong>: Your research topic (e.g., &quot;Latest advances in quantum computing&quot;) - this is only used in the title of the reply</li>
<li><strong>Body</strong>: Detailed research question or context that will be sent to Deep Research</li>
<li><strong>Wait</strong>: The system will process your request and send the research report back to your email</li>
</ol>
<p>The architecture is interesting because Deep Research can take a while to complete. I couldn't just hold the connection open. Instead, I set up a two-part system on Val Town:</p>
<ol>
<li><strong>An Email Handler</strong>: Receives the query and kicks off the job with the Google GenAI API, saving the task ID to a JSON object in valtown blob storage.</li>
<li><strong>A Cron Job</strong>: Periodically checks the status of pending tasks. Once a report is ready, it formats the result as HTML and emails it back to the sender.</li>
</ol>
<p>It's a great example of how serverless functions and simple state storage can build powerful asynchronous agents.</p>
<p>You can fork this project or view the source code directly on Val Town.</p>
<p><a href="https://www.val.town/x/paulkinlan/deep-research-email">View the Code on Val Town</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>fauxmium - browse an infinite imaginary web</title>
      <link>https://paul.kinlan.me/projects/fauxmium/</link>
      <pubDate>Fri, 19 Sep 2025 10:12:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/fauxmium/</guid>
      <description>What if every website you visited didn&#39;t actually exist until the moment you asked for it? What if the entire web was a unique, AI-generated experience, created just for you, on the fly?
That&#39;s the core idea behind my latest project, Fauxmium.
Fauxmium is a proof-of-concept that explores what it&#39;s like to browse an effectively infinite web. Everything you see inside the Fauxmium browser is generated in real-time by AI. It is not real.</description>
      <content:encoded> <![CDATA[<p>What if every website you visited didn't actually exist until the moment you asked for it? What if the entire web was a unique, AI-generated experience, created just for you, on the fly?</p>
<p>That's the core idea behind my latest project, <strong>Fauxmium</strong>.</p>
<p>Fauxmium is a proof-of-concept that explores what it's like to browse an effectively infinite web. Everything you see inside the Fauxmium browser is generated in real-time by AI. It is not real.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/NZ0D2MwNbrM" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

<p>It works by launching a real Chrome browser, but with a twist. It intercepts every navigation and image request and routes them to a local server. That server then prompts a generative AI model to create the HTML and images for the URL you requested, streaming the content back to your browser. The result is a unique, ephemeral, and often surprising browsing session.</p>
<p><strong>➡️ <a href="https://github.com/paulkinlan/fauxmium">Check out Fauxmium on GitHub</a></strong></p>
<p>Getting started is simple. If you have Node.js installed and a GEMINI API Key, you can run it with a single command in your terminal. I'd recommend running it with DevTools open to see what's happening under the hood.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npx fauxmium --devtools
</span></span></code></pre></div><p>Once it launches, just start typing in any URL you can imagine and see what the AI comes up with!</p>
<h3 id="how-it-works-the-gist">How It Works (The Gist)</h3>
<p>Under the hood, Fauxmium combines a few key components:</p>
<ol>
<li><strong>Browser Control:</strong> It uses <strong>Puppeteer</strong> to launch a headful instance of Google Chrome.</li>
<li><strong>Request Interception:</strong> Puppeteer intercepts every request and sends it to a local proxy server.</li>
<li><strong>AI Generation:</strong> The local server uses the Vercel AI SDK and the Google Gemini API to handle requests. It takes the URL you requested and uses it in a prompt to ask an AI model to generate the corresponding HTML page or image.</li>
<li><strong>Rendering:</strong> The generated content is streamed directly back to the browser and rendered.</li>
</ol>
<p>Currently, it supports text generation from <strong>Google (Gemini)</strong>, <strong>OpenAI</strong>, <strong>Anthropic</strong>, and <strong>Groq</strong>, with image generation handled by Gemini. You can tweak the prompts it uses by editing the text files in the <code>prompts/</code> directory—they're re-read on every request, so you can experiment without restarting the server.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>AI Focus</title>
      <link>https://paul.kinlan.me/2025-06-06-ai-focus/</link>
      <pubDate>Fri, 06 Jun 2025 13:14:34 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-06-06-ai-focus/</guid>
      <description>Link: AI Focus
I&#39;ve launched a new blog. It&#39;s called AI Focus, and it&#39;s dedicated to exploring the intersection of artificial intelligence and web development. My goals of the blog is to cover a range of topics that I think are going to affect the web and web development in the coming years.
Why? I think that the nature of the web has changed and it will change even more in the coming years.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://aifoc.us">AI Focus</a></p>
<p>I've launched a new blog. It's called AI Focus, and it's dedicated to exploring the intersection of artificial intelligence and web development. My goals of the blog is to cover a range of topics that I think are going to affect the web and web development in the coming years.</p>
<p>Why? I think that the nature of the web has changed and it will change even more in the coming years. I love the web and web development, but I don't think we can idly sit by and assume that &quot;the web will always win&quot;. I don't &quot;always bet on the web&quot;, I bet on the people who build for the medium that is the web, that is the users, the authors, the developers, the designers, the creators and web browser engineers. I think we are going to have to adapt to the changes that &quot;AI&quot;, or at least the technology that is Large Language Models, bring to the web, and I want to be at the forefront of that change.</p>
<p>I'm experimenting with a more essay focus to the writing which pose more questions than answers at the moment. I've been silently writing articles on it for the past couple of weeks, covering topics like:</p>
<ul>
<li><a href="https://aifoc.us/embedding/">The future of embedding</a>?</li>
<li><a href="https://aifoc.us/a-link-is-all-you-need/">The future of linking</a>?</li>
<li><a href="https://aifoc.us/super-apps/">Will there be a super-app</a>?</li>
</ul>
<p>My buddy <a href="https://aifoc.us/authors/andreban/">Andre Bandarra</a> has also written some great pieces about <a href="https://aifoc.us/ai-powered-site-mashups/">Mashups</a> and <a href="https://aifoc.us/ai-assisted-webdev/">assistance in web development</a></p>
<p>Let me know what you think, and also what topics you'd like to see covered in the future. I'm always looking for new ideas and perspectives, and I want to expand my understanding and opinion.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Annie Sullivan: Diving into the Data on Feature Availability and Adoption</title>
      <link>https://paul.kinlan.me/2025-05-12-diving-into-the-data-on-feature-availability-and-adoption-blinkon-20---youtube/</link>
      <pubDate>Mon, 12 May 2025 14:17:06 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-05-12-diving-into-the-data-on-feature-availability-and-adoption-blinkon-20---youtube/</guid>
      <description>Link: Diving into the Data on Feature Availability and Adoption [BlinkOn 20] - YouTube
This is a great talk from Annie Sullivan at BlinkOn 20 about the availability and adoption of web features. Annie discusses the importance of understanding how features are used in the wild, and how this can inform the development of new features.
I took a few notes on the talk, which I thought were interesting and linked to them below:</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://www.youtube.com/watch?v=DdnFlyx0dU0">Diving into the Data on Feature Availability and Adoption [BlinkOn 20] - YouTube</a></p>
<p>This is a great talk from Annie Sullivan at BlinkOn 20 about the availability and adoption of web features. Annie discusses the importance of understanding how features are used in the wild, and how this can inform the development of new features.</p>
<p>I took a few notes on the talk, which I thought were interesting and linked to them below:</p>
<ul>
<li><strong>Core Web Vitals:</strong> Discusses work on improving web page quality metrics (LCP, INP, CLS) using HTTP Archive data to identify and fix issues <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=21s">[00:00:21]</a>, <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=50s">[00:00:50]</a> and how this was the groundwork for the talk.</li>
<li><strong>Baseline Data:</strong> A baseline dashboard by the WebDX community group provides metadata on web features, including their availability status and <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=212s">[00:03:32]</a> and how Annie would use this aligned with usage data to understand the adoption of web features (rest of the talk)</li>
<li><strong>Key Findings:</strong>
<ul>
<li>Wider availability leads to higher adoption <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=356s">[00:05:56]</a> &lt;-- We all had a feeling this was the case, but it's nice to see it backed up with data.</li>
<li>Feature adoption isn't significantly skewed by website popularity <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=375s">[00:06:15]</a>.</li>
<li>
<ol>
<li>we can do splits on data easily, 2) HTML and JavaScript features show clearer adoption patterns than CSS <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=439s">[00:07:19]</a>.</li>
</ol>
</li>
<li>HTTP Archive data aligns well with real Chrome usage data <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=567s">[00:09:27]</a>.</li>
</ul>
</li>
<li><strong>Analysis Tools:</strong> Uses HTTP Archive and BigQuery to analyze feature usage <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=614s">[00:10:14]</a>.</li>
<li><strong>Adoption Factors:</strong>
<ul>
<li>Site-building platforms (e.g., WordPress) <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=774s">[00:12:54]</a>.</li>
<li>Third-party embeds (e.g., YouTube) <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=532s">[00:08:52]</a>.</li>
<li>Progressive enhancement with fallbacks <a href="http://www.youtube.com/watch?v=DdnFlyx0dU0&amp;t=1063s">[00:17:43]</a>.</li>
</ul>
</li>
</ul>
<p>Our team has long been pushing to understand API usage and how collaborating with site-building platforms can help us understand the impact of our work (selfishly for me), but I do think broadly that this type of insight helps us get more investment in to the web platform and helps us prioritize the right things.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Andre Bandarra: From PyTorch to Browser: Creating a Web-Friendly AI Model</title>
      <link>https://paul.kinlan.me/2025-05-02-from-pytorch-to-browser-creating-a-web-friendly-ai-model/</link>
      <pubDate>Fri, 02 May 2025 12:50:17 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-05-02-from-pytorch-to-browser-creating-a-web-friendly-ai-model/</guid>
      <description>Link: From PyTorch to Browser: Creating a Web-Friendly AI Model
I loved this post from Andre about running sentiment analysis in the browser using a model that he&#39;d trained on embeddings generated from a YouTube comments data was great and shows that you don&#39;t have to run everything through the full language model and instead can use just the embedding APIs to get a decent result.
The client side part of the code can be seen below:</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://bandarra.me/posts/from-pytorch-to-browser-creating-a-web-friendly-ai-model">From PyTorch to Browser: Creating a Web-Friendly AI Model</a></p>
<p>I loved this post from Andre about running sentiment analysis in the browser using a model that he'd trained on embeddings generated from a YouTube comments data was great and shows that you don't have to run everything through the full language model and instead can use just the embedding APIs to get a decent result.</p>
<p>The client side part of the code can be seen below:</p>
<blockquote>
<p>const input = 'Hello, your video is amazing!';
const embedResult = await genAi.models.embedContent({
model: 'text-embedding-004',
contents: [input],
});
const embeddings = embedResult.embeddings.map(embedding =&gt; embedding.values);
const outputTensor = await model.predict(tf.tensor2d(embeddings));
const argmax = await tf.argMax(outputTensor, 1).array();
const labels = ['Positive', 'Neutral', 'Negative'];
const results = argmax.map(i =&gt; labels[i]);
console.log(results);</p>
<p>const probabilities = await tf.softmax(outputTensor, 1).array()
console.log(probabilities);</p>
</blockquote>
]]></content:encoded>
    </item>
    
    <item>
      <title>Michael Lynch: How to Write Blog Posts that Developers Read · Refactoring English</title>
      <link>https://paul.kinlan.me/2025-03-28-how-to-write-blog-posts-that-developers-read-refactoring-english/</link>
      <pubDate>Fri, 28 Mar 2025 19:10:02 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-03-28-how-to-write-blog-posts-that-developers-read-refactoring-english/</guid>
      <description>Link: How to Write Blog Posts that Developers Read · Refactoring English
I think this post would be good for every developer to read. It&#39;s more about communication than just blogging.
The biggest mistake software bloggers make is meandering.
Often, the author has some valuable insight to share, but they squander their first seven paragraphs on the history of functional programming and a trip they took to Bell Labs in 1973.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://refactoringenglish.com/chapters/write-blog-posts-developers-read/">How to Write Blog Posts that Developers Read · Refactoring English</a></p>
<p>I think this post would be good for every developer to read. It's more about communication than just blogging.</p>
<blockquote>
<p>The biggest mistake software bloggers make is meandering.</p>
<p>Often, the author has some valuable insight to share, but they squander their first seven paragraphs on the history of functional programming and a trip they took to Bell Labs in 1973. By the time they get to the part that’s actually interesting, everyone has long since closed the browser tab.</p>
</blockquote>
<p>This is me to a tea. I remember when I first started blogging and I would just get traffic, times have changed and I certainly need to be a lot clearer with my writing.</p>
<p>This really resonated with me:</p>
<blockquote>
<p>If you want people to read your blog, choose topics that have a clear path to your readers. Before you begin writing, think through how readers will find your post.</p>
</blockquote>
<p>I tend to fart out a blog post, and the guidance here is very similar to the guidance I hear from a lot of content creators. Study your medium, understand your audience, how to reach them, and then plan plan plan.</p>
<p>If you're interested in the broader topic of writing for an audience (probably a more business focused one) then I really enjoyed <a href="https://www.barbaraminto.com/">Pyramid Principle by Barbara Minto</a>, it's helped me a lot within my role as a way to communicate more effectively (although, I still have a long way to go).... I've heard it's favoured by McKinsey consultants, while that's not my cup of tea, the principles seem sound to me.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Adam Argyle: Rainbow Shadow Button</title>
      <link>https://paul.kinlan.me/2025-03-21-rainbow-shadow-button-19-march-2025/</link>
      <pubDate>Fri, 21 Mar 2025 23:54:25 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-03-21-rainbow-shadow-button-19-march-2025/</guid>
      <description>Link: Rainbow Shadow Button · 19 March 2025
background: linear-gradient(to right in oklch longer hue, oklch(95% var(--vibrance) 0) 0 100%);
I know CSS pretty well, but I&#39;m mind blown by what Adam produces. It&#39;s like when I draw, I know what I want, but translating it from the image in my brain into something that looks good on paper is a whole other story.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://nerdy.dev/rainbow-shadow-button">Rainbow Shadow Button · 19 March 2025</a></p>
<blockquote>
<p>background: linear-gradient(to right in oklch longer hue, oklch(95% var(--vibrance) 0) 0 100%);</p>
</blockquote>
<p>I know CSS pretty well, but I'm mind blown by what Adam produces. It's like when I draw, I know what I want, but translating it from the image in my brain into something that looks good on paper is a whole other story.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>: Real-world uses of TypeScript’s utility types - Piccalilli</title>
      <link>https://paul.kinlan.me/2025-03-21-real-world-uses-of-typescripts-utility-types---piccalilli/</link>
      <pubDate>Fri, 21 Mar 2025 23:40:19 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-03-21-real-world-uses-of-typescripts-utility-types---piccalilli/</guid>
      <description>Link: Real-world uses of TypeScript’s utility types - Piccalilli
I have a bit of love-hate relationship with TypeScript, but there are parts of the typescript Type system that I really like. This article from Piccalilli is a great introduction to TypeScript&#39;s utility types (which is one of the areas I like, even though it is a bit complex)
Utility types are types that modify other types. You can think of them as functions, but they operate on types instead of values.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://piccalil.li/blog/real-world-uses-of-typescripts-utility-types/">Real-world uses of TypeScript’s utility types - Piccalilli</a></p>
<p>I have a bit of love-hate relationship with TypeScript, but there are parts of the typescript Type system that I really like. This article from Piccalilli is a great introduction to TypeScript's utility types (which is one of the areas I like, even though it is a bit complex)</p>
<blockquote>
<p>Utility types are types that modify other types. You can think of them as functions, but they operate on types instead of values. It’s mind bending at first — especially if you’re coming from languages that don’t have anything similar — but we’re going to walk through a load of examples to see how they work.</p>
</blockquote>
<p>Mind-bending indeed.</p>
<p>One of my favorite types that I've made is <a href="https://github.com/breadboard-ai/breadboard/commit/ef67eed88c3554f2194ce5d8e3a983b45fa80877#diff-ccdffb90d72b12bb1967f030c8defb82761761d6d7553eee711c04c9b6b402c6R107">here</a> in Breadboard. It could take a function and return a proxy for that function that had the same calling parameters.... I think... I'm not sure, I wrote it a while ago... heh, that's the &quot;hate&quot; part of the relationship. :D</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Jeremy Keith: Adactio: Journal—Command and control</title>
      <link>https://paul.kinlan.me/2025-03-21-adactio-journalcommand-and-control/</link>
      <pubDate>Fri, 21 Mar 2025 21:46:38 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-03-21-adactio-journalcommand-and-control/</guid>
      <description>Link: Adactio: Journal—Command and control
I’ve been banging on for a while now about how much I’d like a declarative option for the Web Share API. I was thinking that the type attribute on the button element would be a good candidate for this (there’s prior art in the way we extended the type attribute on the input element for HTML5).
I&#39;m fully aligned with Jeremey on this one, there&#39;s a heap of commands and actions that a user invokes in the browser that should be more accessible from the client and be able to be invoked without JavaScript.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://adactio.com/journal/21803">Adactio: Journal—Command and control</a></p>
<blockquote>
<p>I’ve been banging on for a while now about how much I’d like a declarative option for the Web Share API. I was thinking that the type attribute on the button element would be a good candidate for this (there’s prior art in the way we extended the type attribute on the input element for HTML5).</p>
</blockquote>
<p>I'm fully aligned with Jeremey on this one, there's a heap of commands and actions that a user invokes in the browser that should be more accessible from the client and be able to be invoked without JavaScript.</p>
<p>Turns out there is work being done around this area for <code>dialog</code> and <code>popover</code> elements, with scoping for more elements in the future.</p>
<blockquote>
<p>Things have been rolling along and invoketarget has now become the command attribute (there’s also a new commandfor attribute that you can point to an element with an ID). Here’s a list of potential values for the command attribute on a button element.</p>
<p>Right now they’re focusing on providing declarative options for launching dialogs and other popovers. That’s already shipping.</p>
</blockquote>
<p>More details about what Jeremy is talking about can be found in this <a href="https://developer.chrome.com/blog/command-and-commandfor#the_command_and_commandfor_pattern">developer.chrome.com</a> article.</p>
<p>I agree with where Jeremy wants to see this head, as the article says &quot;We welcome community input—if you have suggestions don't hesitate to file an issue on the <a href="https://github.com/openui/open-ui/issues/new">Open UI Issue Tracker</a>.&quot;</p>
<p>Maybe we can get <code>button command=&quot;bookmark&quot;</code>... I joke... kinda... we used to have an API for this back in the day and while I don't think we would want an API to your bookmarks, I could see a world where a lot of the Fediverse's issues could be solved with a few more declarative commands.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Brad Woods: Browser adaptation</title>
      <link>https://paul.kinlan.me/2025-03-20-browser-adaptation/</link>
      <pubDate>Thu, 20 Mar 2025 15:40:38 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-03-20-browser-adaptation/</guid>
      <description>Link: Browser adaptation
Something that I&#39;ve been thinking about a lot recently is that the medium that is the web. It enables a lot of things that only the web can so this post by Brad Woods really resonated with me and it talks about how the web can be used to adapt stories to different mediums.
Different mediums convey information in unique ways and creators interpret the same story through unique perspectives.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://garden.bradwoods.io/notes/story/browser-adaptation">Browser adaptation</a></p>
<p>Something that I've been thinking about a lot recently is that the medium that is the web. It enables a lot of things that only the web can so this post by Brad Woods really resonated with me and it talks about how the web can be used to adapt stories to different mediums.</p>
<blockquote>
<p>Different mediums convey information in unique ways and creators interpret the same story through unique perspectives. Tolkien gives relatively little detail of what Middle-earth looks like, leaving much of the work to the reader's imagination. Jackson on the other hand paints vivid pictures. Tolkien emphasizes the length of the journey by detailing every step, making readers feel the weight of the adventure. Jackson focuses on the major events, creating a fast-paced narrative. Neither approach is superior. Each explores Middle-earth from a different angle. Adaptations allow audiences to make a deeper connection with the story, enriching their understanding and appreciation of the original work.</p>
</blockquote>
<p>What the browser offers</p>
<blockquote>
<p>The browser offers the following communication tools:</p>
<p>▪text
▪image
▪video
▪sound
▪interactivity
▪video game environments (2D and 3D)
With these, we can recreate most story mediums. For example, using image and text to make a comic book or sound to make a podcast. This gives the creator freedom to choose the communication tool(s) that best fit the story they want to tell.</p>
</blockquote>
<p>Yes. YES. YES! I love it.</p>
<p>He also discusses the idea of <a href="https://garden.bradwoods.io/notes/story/browser-adaptation#:~:text=their%20own%20narrative.-,Personalize,-The%20browser%27s%20localisation">Personalization</a> and <a href="https://garden.bradwoods.io/notes/story/browser-adaptation#:~:text=one%20particular%20telling.-,Interactivity,-Interactivity%20enables%20more">Interactivity</a>. Quadruple yes! The web as a medium is so incredibly versatile and I love seeing people explore that.</p>
<p>In 2014 Jeremy Keith published a post <a href="https://adactio.com/journal/6692">&quot;Continuum&quot;</a> and it touches on a different aspect of the value of the web: it's resilence and ability to adapt to the devices it runs on and the browsers it runs in.</p>
<p>I'm still working on how I want to be able to express the value of the web more, especially in a world where it looks like the youth of today who grew up with mediums such as YouTube, TikTok, Facebook and Instagram don't naturally see the web as a medium that they can use to express themselves (or even enjoy).</p>
<p>As a side note, this blog is a gold mine of neat experiments and explainers</p>
<ul>
<li><a href="https://garden.bradwoods.io/notes/svg/draw-on-scroll">https://garden.bradwoods.io/notes/svg/draw-on-scroll</a></li>
<li><a href="https://garden.bradwoods.io/notes/css/blend-modes">https://garden.bradwoods.io/notes/css/blend-modes</a></li>
<li><a href="https://garden.bradwoods.io/notes/javascript/web-api/intersection-observer">https://garden.bradwoods.io/notes/javascript/web-api/intersection-observer</a></li>
<li><a href="https://garden.bradwoods.io/notes/javascript/web-api/intersection-observer/dynamic-header">https://garden.bradwoods.io/notes/javascript/web-api/intersection-observer/dynamic-header</a></li>
<li><a href="https://garden.bradwoods.io/notes/misc/news">https://garden.bradwoods.io/notes/misc/news</a></li>
<li><a href="https://garden.bradwoods.io/notes/game-dev-journal/03-persistence">https://garden.bradwoods.io/notes/game-dev-journal/03-persistence</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>All of Igalia: Planet Igalia</title>
      <link>https://paul.kinlan.me/2025-02-20-planet-igalia/</link>
      <pubDate>Thu, 20 Feb 2025 10:42:16 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-20-planet-igalia/</guid>
      <description>Link: Planet Igalia
When I was preparing the Interop post yesterday, I found one of the agalia links didn&#39;t work and while I was hunting for the new link, I found Planet Igalia. :mind-blown:
I&#39;ve always wondered what&#39;s happening at Igalia and while I love the podcast, I do love a good Blog.... I&#39;m really not sure how I missed this entire part of the site, but it&#39;s go so much good information and often frequent deep dives in to browsers.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://planet.igalia.com/">Planet Igalia</a></p>
<p>When I was preparing the <a href="https://paul.kinlan.me/2025-02-19-interop-2025-another-year-of-web-platform-improvements-blog-webdev/">Interop post</a> yesterday, I found one of the agalia links didn't work and while I was hunting for the new link, I found <a href="https://planet.igalia.com/">Planet Igalia</a>. :mind-blown:</p>
<p>I've always wondered what's happening at Igalia and <a href="https://www.igalia.com/chats/">while I love the podcast</a>, I do love a good Blog.... I'm really not sure how I missed this entire part of the site, but it's go so much good information and often frequent deep dives in to browsers.  For example Maxsim Sisov's post on &quot;<a href="https://blogs.igalia.com/msisov/unmasking-hidden-floating-points-errors-in-ozone-wayland/">Unmasking Hidden Floating-Point Errors in Chromium’s Ozone/Wayland</a>&quot; - such an awesome read into an area I had zero knowledge about.</p>
<p>Triff and marv.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Nicholas C. Zakas: ESLint now officially supports linting of CSS - ESLint - Pluggable JavaScript Linter</title>
      <link>https://paul.kinlan.me/2025-02-20-eslint-now-officially-supports-linting-of-css---eslint---pluggable-javascript-linter/</link>
      <pubDate>Thu, 20 Feb 2025 10:28:41 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-20-eslint-now-officially-supports-linting-of-css---eslint---pluggable-javascript-linter/</guid>
      <description>Link: ESLint now officially supports linting of CSS - ESLint - Pluggable JavaScript Linter
We feel that validation and enforcing baseline features are the minimum that a linter needs to support in 2025, and so we spent a lot of time making the no-invalid-at-rules, no-invalid-properties, and require-baseline rules as comprehensive as possible.
Love it!! Absolutely love it.
One of the areas that we would love to see more of (and we hope to work) is more integration of Baseline in to developer workflows.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://eslint.org/blog/2025/02/eslint-css-support/">ESLint now officially supports linting of CSS - ESLint - Pluggable JavaScript Linter</a></p>
<blockquote>
<p>We feel that validation and enforcing baseline features are the minimum that a linter needs to support in 2025, and so we spent a lot of time making the no-invalid-at-rules, no-invalid-properties, and require-baseline rules as comprehensive as possible.</p>
</blockquote>
<p>Love it!! Absolutely love it.</p>
<p>One of the areas that we would love to see more of (and we hope to work) is more integration of Baseline in to developer workflows. That could be Linters (like ESLint), boot-strappers, IDEs, compilers, frameworks, DevTools. You name it, I think it would help the entire ecosystem if we could get see increase awareness and usage of Baseline and therefore adoption of the features that all the browsers implement natively in to the hands of developers.</p>
<p>Exciting times.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Lokesh Khurana: How Google Chrome’s autofill feature helps both shoppers and merchants</title>
      <link>https://paul.kinlan.me/2025-02-19-how-google-chromes-autofill-feature-helps-both-shoppers-and-merchants/</link>
      <pubDate>Wed, 19 Feb 2025 16:41:14 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-19-how-google-chromes-autofill-feature-helps-both-shoppers-and-merchants/</guid>
      <description>Link: How Google Chrome’s autofill feature helps both shoppers and merchants
One of Shopify’s key metrics is Checkout Conversion Rate (CCR), which measures the rate of successful checkouts completed over a period of time. Through testing, Shopify found that removing unnecessary steps led to more customers completing their checkouts. Guest checkouts using autofill had a 45% higher CCR than guest checkouts without autofill. Basically, the customers who didn’t have to spend time filling out forms were more likely to actually buy something at the end of their online shopping trip.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://blog.google/products/chrome/chrome-autofill/">How Google Chrome’s autofill feature helps both shoppers and merchants</a></p>
<blockquote>
<p>One of Shopify’s key metrics is Checkout Conversion Rate (CCR), which measures the rate of successful checkouts completed over a period of time. Through testing, Shopify found that removing unnecessary steps led to more customers completing their checkouts. <strong>Guest checkouts using autofill had a 45% higher CCR than guest checkouts without autofill</strong>. Basically, the customers who didn’t have to spend time filling out forms were more likely to actually buy something at the end of their online shopping trip.</p>
</blockquote>
<p>Emphasis mine, but this is incredible and is something that I think has gone under the radar.</p>
<p>For the longest time it was thought that there was no way to measure the impact of autofill on the web. In 2016  I developed a small snippet to <a href="https://paul.kinlan.me/detecting-when-autofill-happens/">detect when autofill happens</a> and while no one paid attention, it's good to see that with the introduction of CSS <code>:autofill</code> pseudo-class in Baseline, <a href="https://web.dev/articles/autofill-measure">we can create some simple JS that will more effectively detect when autofill happens</a>.</p>
<p>There's also now <a href="https://developer.chrome.com/docs/devtools/autofill">better autofill tooling in Chrome DevTools</a>, so my hope is that more people will start to understand the impact of autofill on their sites and start to optimize for it....</p>
<p>... maybe.. There is still a lot of concern in the ecosystem about <code>autocomplete=off</code> and a lot of people that don't want the user's agent to control the autocomplete experience.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Interop 2025: another year of web platform improvements</title>
      <link>https://paul.kinlan.me/2025-02-19-interop-2025-another-year-of-web-platform-improvements-blog-webdev/</link>
      <pubDate>Wed, 19 Feb 2025 12:31:35 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-19-interop-2025-another-year-of-web-platform-improvements-blog-webdev/</guid>
      <description>Link: Interop 2025: another year of web platform improvements
It&#39;s exciting to see the web platform continue to evolve and improve. Interop along with Baseline are aimed to solve some of the top challenges that developers have when building for the web.
Ultimately we only make progress as a platform when the major user-agents make shared and consistent progress on the platform and this has been a huge effort from the teams at Google, Microsoft, Mozilla, and Apple, not to mention Igalia and Bocoup as well as all the developers that have helped to prioritize what is important to them.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://web.dev/blog/interop-2025?hl=en">Interop 2025: another year of web platform improvements</a></p>
<p>It's exciting to see the web platform continue to evolve and improve. Interop along with Baseline are aimed to solve <a href="https://paul.kinlan.me/challenges-for-web-developers/">some of the top challenges</a> that developers have when building for the web.</p>
<p>Ultimately we only make progress as a platform when the major user-agents make shared and consistent progress on the platform and this has been a huge effort from the teams at Google, Microsoft, Mozilla, and Apple, not to mention Igalia and Bocoup as well as all the developers that have helped to prioritize what is important to them.</p>
<p>Here are the announcements from other browsers:</p>
<ul>
<li><a href="https://webkit.org/blog/16458/announcing-interop-2025/">WebKit</a></li>
<li><a href="https://hacks.mozilla.org/2025/02/interop-2025/">Firefox</a></li>
<li><a href="https://blogs.windows.com/msedgedev/2025/02/13/microsoft-edge-and-interop-2025/">Edge</a></li>
<li><a href="https://www.igalia.com/2025/02/13/Interop-2025.html">Igalia</a></li>
<li><a href="https://bocoup.com/blog/interop-2025">Bocoup</a></li>
</ul>
<p>While I've been following the progress on Interop I hadn't noticed that they are looking at new areas of investment:</p>
<blockquote>
<p>In addition, and as in previous years, there's a set of areas for investigation. These are areas where we don't have enough information or tests to include as a focus area, but the group feels some work should be done to get them to a stage where we can include them.</p>
<ul>
<li>Accessibility testing</li>
<li>Gamepad API testing</li>
<li>Mobile testing</li>
<li>Privacy testing</li>
<li>WebVTT</li>
</ul>
</blockquote>
<p>This feels like a good set of areas to focus on. I'm particularly interested in the Accessibility testing and Privacy testing. I think these are areas that are often overlooked and are critical to the success of the web platform.</p>
<p>Finally, I encourage everyone to look towards the tooling that is available because of the work that the Interop group is doing to understand all of the <a href="https://web-platform-dx.github.io/web-features/web-features/">web-features</a> and their availability across the platform.</p>
<blockquote>
<p>You can follow along on the Interop 2025 dashboard at <a href="httos://wpt.fyi/interop-2025">https://wpt.fyi/interop-2025</a> and as things become Baseline Newly available they'll show up in the Baseline 2025 list on <a href="https://webstatus.dev">webstatus.dev</a> too.</p>
</blockquote>
]]></content:encoded>
    </item>
    
    <item>
      <title>WikiTok</title>
      <link>https://paul.kinlan.me/2025-02-17-wikitok/</link>
      <pubDate>Mon, 17 Feb 2025 11:44:53 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-17-wikitok/</guid>
      <description>Link: WikiTok
This is such an amazing site and has become a bit of a daily habit for me. It&#39;s a brilliantly simple idea that means I&#39;m browsing more of Wikipedia than I ever have before.
I built a similar demo a while ago using the now-defunct Portals API because I want to explore what a Web Browser could be if it had a UI like TikTok. My hypothesis was that while links are amazing, what is behind them is hidden, and worse (imo) it&#39;s behind a banner image that is often not representative of the content.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://wikitok.vercel.app/">WikiTok</a></p>
<p>This is such an amazing site and has become a bit of a daily habit for me. It's a brilliantly simple idea that means I'm browsing more of Wikipedia than I ever have before.</p>
<p>I built a similar demo a while ago using the now-defunct Portals API because I want to explore what a Web Browser could be if it had a UI like TikTok. My hypothesis was that while links are amazing, what is behind them is hidden, and worse (imo) it's behind a banner image that is often not representative of the content.</p>
<p>So the idea was that as long as you had a list of sites that you might visit it might be neat to have a way to flick between them quickly and get an idea if you actually want to dive into the content. The portals API seemed like a good way to do it because it could run JS, but was non-interactive and in theory privacy-preserving.</p>
<p>I adapted it in mid-2024 to use generated screenshots of the pages. I also thought I should give it a snappy name. Flick the Web's code is <a href="https://glitch.com/~flick-the-web">here</a> and the you can play with the <a href="https://flick-the-web.glitch.me/">demo</a> that runs over the latest articles on HackerNews.</p>
<p>I'm really glad that WikiTok did this and also it has been <a href="https://github.com/IsaacGemal/wikitok">open sourced</a> as I can totally imagine that this type of UI takes off on the web.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I edit my blogs</title>
      <link>https://paul.kinlan.me/2025-02-17-how-i-edit-my-blog/</link>
      <pubDate>Mon, 17 Feb 2025 11:20:41 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-17-how-i-edit-my-blog/</guid>
      <description>It may come as no surprise to the people who get emails from me, but I failed GCSE English. I loved reading, but I struggled to articulate my thoughts clearly and I struggled with basic grammar. It wasn&#39;t until I was 20 that I could explain a verb and a noun.... A good friend read my final year dissertation about Fraud Detection and his main comment was: &amp;quot;Did you learn how to use a semi-colon?</description>
      <content:encoded> <![CDATA[<p>It may come as no surprise to the people who get emails from me, but I failed GCSE English. I loved reading, but I struggled to articulate my thoughts clearly and I struggled with basic grammar. It wasn't until I was 20 that I could explain a verb and a noun.... A good friend read my final year dissertation about Fraud Detection and his main comment was: &quot;Did you learn how to use a semi-colon?&quot;; No. I barely know how to use, a comma.</p>
<p>The web has always been a great place for me to explore writing and sharing my thoughts, and it was a large part in getting me a Developer Relations role at Google. This site is my own little corner of it that I can control. When I get the time, I try to write about things that I find interesting. You might find it interesting, you might not. I don't really mind, I just like to try and improve my writing.</p>
<p>I do get the occasional email from people who read my blog and they ask me how I write and edit my blog. I thought I would write a little bit about it.</p>
<p>My blog infrastructure is pretty simple. It's a mostly static site built with <a href="https://gohugo.io/">Hugo</a>. I chose Hugo because it takes 3 seconds to build my entire site and I really value the rapid local building and near instant deployment. All of the other Ruby and JavaScript builders would take sometimes over a minute.</p>
<p>Because my site is not amazingly dynamic, I felt that I didn't really need Wordpress, but I did need a tool for editing.</p>
<p>The hosting is on Vercel, but I've no affinity for it. I just needed something that could host a static site and get it built and hosted in seconds (which Vercel does).</p>
<p>Because Hugo is mostly driven by Markdown I used a tool called <a href="https://app.spinalcms.com/">SpinalCMS</a> as my editor because it had a GUI and could import my archives, but there were a couple of bugs in it (It doesn't use a date format that works well with Hugo) and text editing (try delete past a paragraph). So I built a simple in about 30 minutes called... well, it doesn't really have a name it's just a URL <a href="https://blog-craft-editor-paulkinlan.replit.app/">https://blog-craft-editor-paulkinlan.replit.app/</a></p>
<p>The editor is just a static page that uses <a href="https://tiptap.dev/">TipTap</a> as the actual bit you write in (seriously, TipTap is a great editor API). I have some little bits around the editor that can load and save files back the the filesystem and suggest titles and descriptions, but that's it.</p>
<p>The only other thing is <a href="https://paul.kinlan.me/my-drafts">I have list of things that I've been meaning to complete</a>. I may never actually complete any of these posts.</p>
<p>For the site itself, I really want to encourage people to speak to me, but there's not always a lot of engagement. I use <a href="http://commento.io/">commento.io</a> and it works well.</p>
<p>And that's about it really.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Addy Osmani: Why I use Cline for AI Engineering - by Addy Osmani</title>
      <link>https://paul.kinlan.me/2025-02-17-why-i-use-cline-for-ai-engineering---by-addy-osmani/</link>
      <pubDate>Mon, 17 Feb 2025 10:50:41 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-02-17-why-i-use-cline-for-ai-engineering---by-addy-osmani/</guid>
      <description>Link: Why I use Cline for AI Engineering - by Addy Osmani
In this post Addy describes his use of Cline (Jan 30). It was the first time I&#39;d heard of it. I was kinda surprised because I&#39;ve been on top of tooling for a while now.
For the longest time I&#39;d been using Replit, it had a nice flow to it. I could ask it to help me solve a problem and it would just apply the code to the project.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://addyo.substack.com/p/why-i-use-cline-for-ai-engineering">Why I use Cline for AI Engineering - by Addy Osmani</a></p>
<p>In this post Addy describes his use of Cline (Jan 30). It was the first time I'd heard of it. I was kinda surprised because I've been on top of tooling for a while now.</p>
<p>For the longest time I'd been using Replit, it had a nice flow to it. I could ask it to help me solve a problem and it would just apply the code to the project. For me, it was a nice way to get things done.</p>
<p>I liked Replit, <a href="https://paul.kinlan.me/generated-web-apps/">I built a lot of apps with it</a>, however their hosting is expensive and their new checkpoint based pricing model frustrated me, especially when it made very clear mistakes.</p>
<p>At the time (like two weeks ago) Copilot didn't apply any changes to the code, it just suggested things which I had to then copy and paste... and even then the suggestions just weren't that good.</p>
<p>Cline has replaced both Copilot and Replit.</p>
<p>I don't mind paying for the use of an LLM and as noted by Addy:</p>
<blockquote>
<p>I’ve also deeply appreciated Cline’s proactive accounting of cost during a session. This is most notable when switching between model providers:</p>
</blockquote>
<p>I love that I can see where the costs are coming from. I can see how much I am spending on the model and how much I am spending on the compute. It's a nice touch.</p>
<p>When you add this with something else Addy points out:</p>
<blockquote>
<p>The DeepSeek-R1 + Sonnet hybrid approach
Recent benchmarks and user experiences have shown that combining DeepSeek-R1 for planning with Claude 3.5 Sonnet for implementation can reduce costs by up to 97% while improving overall output quality.</p>
</blockquote>
<p>It's a pretty compelling solution.</p>
<p>My own workflow at the moment has be to plan with R1 (via OpenRouter) and then implement with Sonnet. I personally don't look at the costs, but I do inspect the output a lot and this has felt like a pretty good balance for my personal projects.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Describing sites instead of coding them</title>
      <link>https://paul.kinlan.me/describing-sites-instead-of-coding-them/</link>
      <pubDate>Fri, 24 Jan 2025 14:13:04 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/describing-sites-instead-of-coding-them/</guid>
      <description>I&#39;ve been noodling on how we might lower the cost to create prototypes of sites for people who aren&#39;t technical. When I speak to a lot of people about websites, they kinda know what they want and can describe it, but it&#39;s a lot of work for them to get a functional skeleton of a site working, so ultimately they don&#39;t build that site or they just go and post something on facebook instead.</description>
      <content:encoded> <![CDATA[<p>I've been noodling on how we might lower the cost to create prototypes of sites for people who aren't technical. When I speak to a lot of people about websites, they kinda know what they want and can describe it, but it's a lot of work for them to get a functional skeleton of a site working, so ultimately they don't build that site or they just go and post something on facebook instead.</p>
<p>Based on my experiences with <a href="http://replit.com">replit.com</a>, <a href="http://websim.ai">websim.ai</a> and a host of other tools, it feels like it's possible to convert a description of a site into a functioning experience. In fact, the more I play with this type of tooling the more I center on the <a href="https://paul.kinlan.me/the-spec-is-important/">importance of the specification of the thing</a> you want.</p>
<p>For example, if I have the following prompt. I would expect a site with three pages, an index page, a projects page and about page.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>The content will be about Paul Kinlan, a Web Developer and lead of the Chrome Developer Relations team. 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Headshot url: https://paul.kinlan.me/images/me.png
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>The site should have a marquee resume page that provides a summary of who he is.
</span></span><span style="display:flex;"><span>An about page with contact details (Email: paul@kinlan.me, Twitter: @paul_kinlan)
</span></span><span style="display:flex;"><span>A page about his project. It should have a list of projects:
</span></span><span style="display:flex;"><span> + reactive-prompt - https://github.com/paulkinlan/reactive-prompt - prompts that react to changes.
</span></span><span style="display:flex;"><span> + f - a LLM to JS library - https://github.com/paulkinlan/f
</span></span><span style="display:flex;"><span> + reactive-agents - https://github.com/paulkinlan/reactive-agents - build agents that react to input
</span></span></code></pre></div><p>Maybe something like <a href="https://web-forge.replit.app/">this</a> to should be created.</p>
<p>Oh!</p>
<p><a href="https://github.com/PaulKinlan/AiWebForge">https://github.com/PaulKinlan/AiWebForge</a> is the tool that i've built that takes a prompt and creates a runnable site. The theory is that there should never be any manual changes to the server, it should all be specified and defined in a prompt.txt file.</p>
<p>One thing that is working quite well is as the site and the content is fetched and LLM'd into existence, it's then used as context for future requests. This then helps with consistency of things like styles across the site.</p>
<p>What I like about this is that I can very rapidly iterate on ideas, and for the most part the output is what I want.</p>
<p>Now, don't get me wrong there are lots of issues with this right now:</p>
<ul>
<li>It's not super quick, so I wouldn't really want to serve this. On the first run for each file it has to pass the entire site through the LLM.</li>
<li>It doesn't reuse as much shared logic from other files</li>
<li>While the data is cached, once its evicted from the cache a subsequent fetch could render completely different content</li>
<li>I ask the LLM to create accessible content and follow best practices, but does it?</li>
<li>What are the energy cost to generate this? I can't work that out and I don't know if it is more or less than the energy requirements of the people building sites</li>
<li>It doesn't yet support server side logic, so you can't have a form that posts to a server</li>
</ul>
<p>Thinking further on that last point and the work that I did on <a href="https://paul.kinlan.me/projects/f/">Runnable JS functions from a prompt</a>, I would love to be able to introduce server runtime functionality. If the site needs a web-form, can we describe what the site needs to collect and how to store it and when the site runs it will just auto-generate the functionality (correctness aside :))</p>
<p>I really like the concept that Ben Thompson talks about (mostly in the context of VR) of just-in-time UI, there's a world where this type of tool could be useful for building a simple UI that gets a very simple task done at run-time.</p>
<p>If you get chance, have a play and let me know what you think.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Dion Almaer: English will become the most popular development language in 6 years</title>
      <link>https://paul.kinlan.me/2025-01-22-english-will-become-the-most-popular-development-language-in-6-years/</link>
      <pubDate>Wed, 22 Jan 2025 11:51:54 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-01-22-english-will-become-the-most-popular-development-language-in-6-years/</guid>
      <description>This great post by Dion &amp;quot;English will become the most popular development language in 6 years&amp;quot; is worth a mull imho.
There&#39;s obviously a lot of push back on LLM&#39;s be it what they were trained on and how much energy they use. However the technology is here, and Dion poses a great question: Will natural language become the way people control their computers?
Two things resonated with me:
The reason that we see so many applications pop up with a chat side bar is a signal that we are building bridges between the computers and the humans in natural language ways.</description>
      <content:encoded> <![CDATA[<p>This great post by Dion &quot;<a href="https://blog.almaer.com/english-will-become-the-most-popular-development-language-in-6-years/">English will become the most popular development language in 6 years</a>&quot; is worth a mull imho.</p>
<p>There's obviously a lot of push back on LLM's be it what they were trained on and how much energy they use. However the technology is here, and Dion poses a great question: Will natural language become the way people control their computers?</p>
<p>Two things resonated with me:</p>
<blockquote>
<p>The reason that we see so many applications pop up with a chat side bar is a signal that we are building bridges between the computers and the humans in natural language ways.</p>
</blockquote>
<p>and</p>
<blockquote>
<p>Future: your English is the source, and as your computer systems improve, they can be regenerating new and improved implementations. It behooves you to invest in testing and validation in this world, but this is something that is actually really needed any way… we just sometimes get away without doing it.</p>
</blockquote>
<p>I maintain a <a href="https://paul.kinlan.me/generated-web-apps">list of apps</a> that I'm happy for people to use. I've also got a huge list of <a href="https://paul.kinlan.me/the-disposable-web">disposable apps</a> that I've built for myself. I'm pretty certain that &quot;natural language&quot; as the main development language will happen at some point and as it developes millions more people will have the ability to control their compute in ways that echo how Spreadsheets enabled people to manipulate their data in their businesses.</p>
<p>Dion centers some of his discussion on Chat-first vs Spec-first. I agree that the <a href="https://paul.kinlan.me/the-spec-is-important/">Spec is important</a>, I just don't know how this part will develop. Do we develop the spec completely up-front, or is there a chat-like assistant that accretes the spec as we develop. I'm thinking the later, I can imagine a world where we have a set of <a href="https://paul.kinlan.me/projects/the-critic/">critics</a> that attempt to objectively look at your spec and tell you what's missing or what could be developed further.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Email - The Web&#39;s Forgotten Medium</title>
      <link>https://paul.kinlan.me/email-the-webs-forgotten-medium/</link>
      <pubDate>Tue, 21 Jan 2025 09:15:30 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/email-the-webs-forgotten-medium/</guid>
      <description>I fondly remember the early days of the web. I remember my first time on the Web in an Internet cafe with my friend Bob, patiently waiting for the only thing we knew to load (cnn.com). It was a time intertwined with a lot of other personal discoveries: ICQ, uh-oh and a/s/l; software modems that didn&#39;t work with my Cyrix P150+, Quake @ 2fps on the same computer; and a thing called email via hotmail.</description>
      <content:encoded> <![CDATA[<p>I fondly remember the early days of the web. I remember my first time on the Web in an Internet cafe with my friend Bob, patiently waiting for the only thing we knew to load (cnn.com). It was a time intertwined with a lot of other personal discoveries: ICQ, <a href="https://www.youtube.com/watch?v=6iCPIUGnHQ8">uh-oh</a> and a/s/l; software modems that didn't work with my Cyrix P150+, Quake @ 2fps on the same computer; and a thing called email via hotmail.</p>
<p>While Quake is something I still love to bash on once in a while, the Web and Email are the two main technologies that are still pervasive in my day to day life.</p>
<p>When people ask me what I do, firstly it's a bit hard to say &quot;Developer Relations manager&quot; and have anyone know what that means. So I say that I'm a Web Developer or Software Engineer, and while it's true and I still build a lot of sites, a huge amount of my time is spent in email.</p>
<p>The Web and Email are both the technological basis for my career to date. Yes, I have to deal with huge amounts of SPAM. I get unearthly amounts of mail from my colleagues and I still struggle to manage my inbox (Anyone want to subscribe to my &quot;Inbox 9000&quot; course?)</p>
<p>I might be alone in this, but one of the ways that I &quot;browse the web&quot; is not inside a browser but inside my email. I get hundreds of Newsletters (Substacks and otherwise) a week, each providing content that I find valuable that is hosted on a URL, but is completely undiscoverable by things like search. While I still love RSS, email is the only way that I am getting this information in a unified interface.</p>
<p>I always liked email as an interface for performing actions too. It's asynchronous nature is built in to how we expect to use it and you never know if there is a person or a machine on the other end of the email address. Over the years I've built some basic services, but more recently I've been on a bit of a rampage. I've built services that <a href="https://paul.kinlan.me/projects/email-summary-service/">Summarize the newsletters</a> for me; provides a <a href="https://www.val.town/v/paulkinlan/criticEmail">critique</a> of the email; generates images based on a subject; <a href="https://sendvia.me/">Sends newsletters and PDF's to my reMarkable</a>; A service that fetches web pages for me; A service that reminds me at certain times... the list goes on.</p>
<p>While I build these tools, I just don't see many services built on the back of email as an interface. Friendfeed was one of the first services that <a href="http://blog.friendfeed.com/2009/04/whole-new-friendfeed.html">I vividly remember letting you perform actions via email</a> and Google App Engine also had an inbound email handler (long-since deprecated), there are perilously few services that I can find that are built off the back of this communication modality. Yes, there are ticket systems for support etc, but outside of that we tend to focus our UIs' on Apps and Sites. (If you know of any services that use email as their input mechanism, leave a comment).</p>
<p>I think the issue stems from two things:</p>
<ol>
<li>
<p>There's just not many services that do inbound Email-&gt;Webhook. I was sad when Google App Engine cancelled their inbound email service. I did end up using SendGrid, and it's... fine.</p>
</li>
<li>
<p>Munging text and extracting meaning from that text has been almost impossible.</p>
</li>
</ol>
<p>I'm hopeful with the progress in Large Language Models we might just be able to see more services use email as the primary interaction modality. Both from a text processing side, to also performing actions based on the content. Some people might say this is Agentic (email's very asynchronous model helps a lot - send an email, some action is performed by a machine or a person, and the result sent back)... Actually, I do too. I don't understand why we sit watching text stream in over a chat interface, I personally just want to fire off an email and get a result as I get on with my day.</p>
<p>I'm also hopeful these types of tools will get easier to deploy too. <a href="http://val.town">val.town</a> is an amazing service and has the concept of inbound email &quot;vals&quot;. Vals are small chunks of TypeScript/JS attached to an email address that when emailed will run. It's really that simple. It's beautiful in fact because it's instant to get it hosted.</p>
<p>Combining both LLM's and the ease of deployment, I do hope for a world where email becomes an even more useful, open, cross-platform tool for discovery and action.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Webkit.org: The success of Interop 2024!</title>
      <link>https://paul.kinlan.me/2025-01-20-the-success-of-interop-2024-webkit/</link>
      <pubDate>Mon, 20 Jan 2025 19:29:53 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-01-20-the-success-of-interop-2024-webkit/</guid>
      <description>Link: https://webkit.org/blog/16413/the-success-of-interop-2024
I saw The success of Interop 2024! in Stefan Judis&#39;s Web Weekly Newsletter.
Jen Simmons at Apple on WebKit pulled together this great post about the progress that has been made in 2024.
In 2024, there were 17 such focus areas: Accessibility, CSS Nesting, Custom Properties, Declarative Shadow DOM, font-size-adjust, HTTPS URLs for WebSocket, IndexedDB, Layout, Pointer and Mouse Events, popover, Relative Color Syntax, requestVideoFrameCallback, Scrollbar Styling, @starting-style &amp;amp; transition-behavior, Text Directionality, text-wrap: balance and URL.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://webkit.org/blog/16413/the-success-of-interop-2024">https://webkit.org/blog/16413/the-success-of-interop-2024</a></p>
<p>I saw <a href="https://webkit.org/blog/16413/the-success-of-interop-2024">The success of Interop 2024!</a> in Stefan Judis's <a href="https://webweekly.email/">Web Weekly</a> Newsletter.</p>
<p>Jen Simmons at Apple on WebKit pulled together this great post about the progress that has been made in 2024.</p>
<blockquote>
<p>In 2024, there were 17 such focus areas: Accessibility, CSS Nesting, Custom Properties, Declarative Shadow DOM, font-size-adjust, HTTPS URLs for WebSocket, IndexedDB, Layout, Pointer and Mouse Events, popover, Relative Color Syntax, requestVideoFrameCallback, Scrollbar Styling, @starting-style &amp; transition-behavior, Text Directionality, text-wrap: balance and URL.</p>
</blockquote>
<p>The value of Interop is so important. It's the reason that the web has been so successful. It's the reason that we can build things that work across all devices and all browsers. It's the reason that we can build things that work for everyone and I'm grateful for the collaboration between all the browser vendors on this.</p>
<p>We just have to be careful to say &quot;the web is XX% interoperable&quot; when the data in the interop project is a percentage of the shared focus areas, not of the entire platform. <a href="https://wpt.fyi/interop-2024">The dashboard</a> is pretty clear, the actual situation of wider interoperability <a href="https://wpt.fyi/results/?label=experimental&amp;label=master&amp;aligned">has a long way to go</a>.</p>
<p>Either way, we should celebrate this progress. The web is getting better.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Simon Willison: My approach to running a link blog</title>
      <link>https://paul.kinlan.me/2025-01-20-my-approach-to-running-a-link-blog/</link>
      <pubDate>Mon, 20 Jan 2025 19:09:03 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/2025-01-20-my-approach-to-running-a-link-blog/</guid>
      <description>Link: My approach to running a link blog
I really like Simon&#39;s approach to running a link blog and his principles really resonate with me
I always include the names of the people who created the content I am linking to, if I can figure that out. Credit is really important, and it’s also useful for myself because I can later search for someone’s name and find other interesting things they have created that I linked to in the past.</description>
      <content:encoded> <![CDATA[<p>Link: <a href="https://simonwillison.net/2024/Dec/22/link-blog/">My approach to running a link blog</a></p>
<p>I really like Simon's approach to running a link blog and his principles really resonate with me</p>
<blockquote>
<p>I always include the names of the people who created the content I am linking to, if I can figure that out. Credit is really important, and it’s also useful for myself because I can later search for someone’s name and find other interesting things they have created that I linked to in the past. If I’ve linked to someone’s work three or more times I also try to notice and upgrade them to a dedicated tag.</p>
</blockquote>
<p>Lifting people up is something that I've always valued (and valued when folks did it on my content). I probably lost my way at the start of my DevRel career - parts of the DevRel job ladder include being Industry influential. I took that to mean being an expert in web development and while I think I'm reasonable and I've built a great team, I love seeing other people succeed and I love sharing their work.</p>
<blockquote>
<p>I try to add something extra. My goal with any link blog post is that if you read both my post and the source material you’ll have an enhanced experience over if you read just the source material itself.</p>
</blockquote>
<p>This was actually something I struggled with in my first iteration of my link blog. I'm still not sure I can always provide more value than the original author but also I have a hunch that linking out of sites is a dying art.</p>
<p>Simon also had a bit about the technology behind his link blog:</p>
<blockquote>
<p>The technology behind my link blog is probably the least interesting thing about it. It’s part of my simonwillisonblog Django application—the main model is called Blogmark and it inherits from a BaseModel defining things like tags and draft modes that are shared across my other types of content (entries and quotations).</p>
</blockquote>
<p>This blog is entirely static (Hugo) and I've been butting my head up against the wall. Static is neat, but it's not enough. If you want to add Activity Pub, well you have to bend Hugo a long way. Add a link blog? Well, that's not too hard given it's structure but it also means having to make a full git-commit to the repo, and this was something that slowed me down last time.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>f - JS functions from a prompt</title>
      <link>https://paul.kinlan.me/projects/f/</link>
      <pubDate>Fri, 17 Jan 2025 14:12:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/f/</guid>
      <description>My buddy Dion made a great post &amp;quot;English will become the most popular development language in 6 years&amp;quot;. Dion&#39;s vision is pretty expansive, but it really tickled my brain.
I&#39;ve been creating a fair number of web-apps with Replit. These &amp;quot;agents&amp;quot; are awesome, they take me through the entire app process. But what if I just want a small bit of my application written in English? How far can we get?</description>
      <content:encoded> <![CDATA[<p>My buddy Dion made a great post &quot;<a href="https://blog.almaer.com/english-will-become-the-most-popular-development-language-in-6-years/" title="https://blog.almaer.com/english-will-become-the-most-popular-development-language-in-6-years/">English will become the most popular development language in 6 years</a>&quot;. Dion's vision is pretty expansive, but it really tickled my brain.</p>
<p>I've been <a href="https://paul.kinlan.me/generated-web-apps/" title="https://paul.kinlan.me/generated-web-apps/">creating a fair number of web-apps with Replit.</a> These &quot;agents&quot; are awesome, they take me through the entire app process. But what if I just want a small bit of my application written in English? How far can we get?</p>
<p><code>f</code> is a <a href="https://www.npmjs.com/package/@paulkinlan/f" title="https://www.npmjs.com/package/@paulkinlan/f">small JavaScript library</a> that I've created to play around with the idea of programs defined with natural language.</p>
<pre tabindex="0"><code>const isOdd = await f`is a given number odd?`;
console.log(isOdd(101))
</code></pre><p>Fun! who needs npm when when you can use an entire LLM to determine if a number is Odd or not :D</p>
<p>It can get a fair bit more complex. How about fetching data from a URL?</p>
<pre tabindex="0"><code>const spaceData = await f`fetch JSON from https://api.spaceflightnewsapi.net/v4/articles/`:
const news = await spaceData();
</code></pre><p>This will (in most cases) create a function that will fetch and return the data from the given URL.</p>
<p>Wouldn't it be nice if we could create a URL from this data?</p>
<pre tabindex="0"><code>// Describe the data structure so the the UI prompt has a better idea of what to build.

const schema = await f`Return a JSON Schema for a given object. The schema should be in the format defined in https://json-schema.org/understanding-json-schema/reference/object.html and should include all the properties of the object. The schema should include the type of the property, the format of the property, the required status of the property, and the description of the property. The schema should include all the properties of the object. The schema should include the type of the property, the format of the property, the required status of the property, and the description of the property.`;

// Describe the data
const schemeDescription = schema(spaceData);

const spaceUI = await f`Using the data defined in &lt;output&gt; create a UI that will best display the space flight information. The developer will provide the data as a parameter and it will be in the format defined in &lt;output&gt;.
    
&lt;output&gt;${JSON.stringify(schemeDescription)}&lt;/output&gt;`;

document.body.appendChild(spaceUI(spaceData));
</code></pre><p><img src="/images/Screenshot%202025-01-17%20at%2014.28.38.png" alt="Screenshot 2025-01-17 at 14.28.38.png">Fun!</p>
<p>I don't know if this is the future. You are heavily reliant on the LLM being good (Claude has consistently good results) - Gemini Nano and Code Llama struggle to generate valid results. Outside of simple functions it's also hard (for me) to get consistent results, so each time that you run the function it might produce a different program.</p>
<p>Full example:</p>
<pre tabindex="0"><code>import { create } from &#34;@paulkinlan/f&#34;;
import {
    prompt,
    ClaudePromptConfiguration
} from &#34;@paulkinlan/reactive-prompt/claude&#34;;

const config = new ClaudePromptConfiguration({ debug: true });

config.dangerouslyAllowBrowser = true;
config.key = window.prompt(&#34;API Key&#34;);

// Because we can use many different LLMs we need to pick one.
const f = create(
  prompt,
  config
);

const fetchNews = await f`fetch JSON from https://api.spaceflightnewsapi.net/v4/articles/`;

// Get the news.
const news = await fetchNews();

// If we can describe the object UI gnereation is a bit easier.
const schema = await f`Return a JSON Schema for a given object. The schema should be in the format defined in https://json-schema.org/understanding-json-schema/reference/object.html and should include all the properties of the object. The schema should include the type of the property, the format of the property, the required status of the property, and the description of the property. The schema should include all the properties of the object. The schema should include the type of the property, the format of the property, the required status of the property, and the description of the property.`;

const schemeDescription = schema(news);

const spaceUI = await f`Using the data defined in &lt;output&gt; create a UI that will best display the space flight information. The developer will provide the data as a parameter and it will be in the format defined in &lt;output&gt;.
    
&lt;output&gt;${JSON.stringify(schemeDescription)}&lt;/output&gt;`;

document.body.appendChild(spaceUI(news));
</code></pre><p>Code is <a href="https://github.com/paulkinlan/f">here</a> - give it a go and let me know if you have any questions or ideas how you might use this (or not use it)</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Email Summary Service</title>
      <link>https://paul.kinlan.me/projects/email-summary-service/</link>
      <pubDate>Wed, 08 Jan 2025 19:37:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/email-summary-service/</guid>
      <description>I get a lot of newsletters, and I love them. They often contain a wealth of deep insight that I can&#39;t easily find while browsing the web. Yes, there are RSS feeds that publish their protected content, but since I spend so much time in my email, I effectively use it as a way of browsing.
I really wanted a way to get quick summaries of the newsletters I read, so I built one.</description>
      <content:encoded> <![CDATA[<p>I get a lot of newsletters, and I love them. They often contain a wealth of deep insight that I can't easily find while browsing the web. Yes, there are RSS feeds that publish their protected content, but since I spend so much time in my email, I effectively use it as a way of browsing.</p>
<p>I really wanted a way to get quick summaries of the newsletters I read, so I built one. <a href="http://Val.town" title="http://Val.town">Val.town</a> is an amazing platform; you can quickly create an HTTP endpoint, a cron job, or an email handler and publish it to the web in milliseconds.</p>
<p>The email handler alone is incredibly powerful, it's something that App Engine used to do and you can do with SendGrid if you have the time to set it all up. With val.town, it's just a function and deploy.</p>
<p>Send an email to <a href="mailto:paulkinlan.emailSummaryHandler@valtown.email" title="mailto:paulkinlan.emailSummaryHandler@valtown.email">paulkinlan.emailSummaryHandler@valtown.email</a>, and in a couple of seconds, you'll get back a summary of its contents.</p>
<iframe width="100%" height="400px" src="https://www.val.town/embed/paulkinlan/emailSummaryHandler" title="Val Town" frameborder="0" allow="web-share" allowfullscreen></iframe>
<p>Given the asynchronous, text-based nature of email, I'm surprised there haven't been more tools built that interact with it using LLMs.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Full RSS feed</title>
      <link>https://paul.kinlan.me/projects/full-rss-feed/</link>
      <pubDate>Sat, 21 Dec 2024 23:07:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/full-rss-feed/</guid>
      <description>I love RSS feeds (and ATOM too (and JSONFeed)) but one thing that frustrates me is when a feed doesn&#39;t include all of the content.
https://full-rss.deno.dev was created to partially solve this. If there is a feed that you really wish was full-content and isn&#39;t then you can replace that feed with https://full-rss.deno.dev/?url=https://developer.chrome.com/static/blog/feed.xml and it will get the 10 most recent posts, scrape the linked page and replace the content with a stripped down version of the full HTML page.</description>
      <content:encoded> <![CDATA[<p>I love RSS feeds (and ATOM too (and JSONFeed)) but one thing that frustrates me is when a feed doesn't include all of the content.</p>
<p><a href="https://full-rss.deno.dev" title="https://full-rss.deno.dev">https://full-rss.deno.dev</a> was created to partially solve this. If there is a feed that you really wish was full-content and isn't then you can replace that feed with <a href="https://full-rss.deno.dev/?url=https://developer.chrome.com/static/blog/feed.xml" title="https://full-rss.deno.dev/?url=https://developer.chrome.com/static/blog/feed.xml">https://full-rss.deno.dev/?url=https://developer.chrome.com/static/blog/feed.xml</a> and it will get the 10 most recent posts, scrape the linked page and replace the content with a stripped down version of the full HTML page.</p>
<p>There are some caveats though:</p>
<ol>
<li>
<p>if the feed is an ATOM feed it will accept it but the output will be RSS.</p>
</li>
<li>
<p>if the linked page uses JS to render content, it will not be included. I didn't want to spin up a puppeteer instance</p>
</li>
<li>
<p>if a post is updated after the entry has been fetched and parsed by a user it will not be fetched again (I need to work something out here)</p>
</li>
</ol>
<p>Source: <a href="https://github.com/PaulKinlan/full-rss" title="https://github.com/PaulKinlan/full-rss">https://github.com/PaulKinlan/full-rss</a></p>
<p>If you are interested in how it was built, here is a rough outline. It uses Deno and Deno Deploy. On the first fetch of a post the system hasn't seen before it will fetch the html, convert it to markdown and then gzip compress it so it can be stored it in Deno KV and not breach the 16kb limit. Then for each entry it will pull the content from the Deno KV store, decompress it and render to HTML.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>When generating apps the spec is important</title>
      <link>https://paul.kinlan.me/the-spec-is-important/</link>
      <pubDate>Tue, 12 Nov 2024 10:42:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/the-spec-is-important/</guid>
      <description>Generating web apps with AI agents like Replit is incredibly powerful, enabling rapid prototyping and deployment.  My experience building tldr.express, a personalized RSS feed summarizer, highlighted the importance of a detailed specification. While initial prompts yielded impressive results, I iteratively refined the app through configuration and additional prompts to address issues like email integration, AI model selection, output formatting, spam prevention, and bot mitigation.  This iterative process reinforced that while AI agents excel at rapid generation, a well-defined specification upfront is crucial for a successful outcome.</description>
      <content:encoded> <![CDATA[<p>I've been <a href="https://paul.kinlan.me/generated-web-apps/" title="https://paul.kinlan.me/generated-web-apps/">generating a lot of web apps</a> recently. It's been exhilarating to be able to launch projects (albeit on the small side) that I've always wanted to see be created finally be willed into existence.</p>
<p>Last week on the train back from London I embarked on my largest project yet, <a href="https://tldr.express" title="https://tldr.express">https://tldr.express</a>. I've been amazed by Agents like <a href="https://replit.com/" title="https://replit.com/">https://replit.com/</a>'s that can scaffold out full working experience that have user account registration and deploy in minutes. But how far can I push it?</p>
<p>I love blogs. It's one of the reasons that enabled me to get in to Developer Relations. I could connect with people and learn from them and I could share my own learnings (I think I've helped folks)... When I was younger and the blogging scene predated social media, I would trawl Technorati trying to find new feeds and I would refresh my feeds every hour to see if anything new had been posted. Alas, times change, my own time to consume content is getting limited but I still try and make sure that I can track what web developers in the community are creating.</p>
<p><a href="http://tldr.express" title="http://tldr.express">tldr.express</a> is a site meant just for me (but anyone can use it) that tries to create a dashboard of the feeds that you care about the most and send you a summary every morning. It's a tool that I've always wanted. But this isn't the important bit.</p>
<p>I was amazed how far my first prompt got me.</p>
<p><img src="/images/replit-prompt.png" alt="Replit prompting"></p>
<p>It created this plan which looked decent.</p>
<p><img src="/images/replit-confirm.png" alt="Replit confirming plan"></p>
<p>It scaffolded out a system that would let me register a user, add feeds and have it summarize the content and display them in a nice overview.</p>
<p><img src="/images/replit-rss-monitor.png" alt="Replit rss monitor"></p>
<p>It wasn't all plain sailing.</p>
<ul>
<li>
<p>The email didn't work, I don't want to think about SMTP etc. Huh. You know, I didn't specify it. Please use resend.com (boom!)</p>
</li>
<li>
<p>It kept asking to use Open AI... Huh. You know, I didn't specify clearly to use Gemini (long context windows and price)</p>
</li>
<li>
<p>It output raw markdown in the UI. Huh. You know, I didn't specify clearly what the output should look like.</p>
</li>
<li>
<p>It displayed in processing order and not date of creation.. Huh You know...</p>
</li>
</ul>
<p>All of this without me touching a single line of code. After a day of use, I realised that if anyone found this site and signed up with an email address, it would be easy for them to spam folks. I probably should have specified that we need to validate email addresses.</p>
<p><img src="/images/replit-validate.png" alt="Replit validate email"></p>
<p>... and it worked pretty much straight away.</p>
<p>On monday this week I thought I would check the user database... wow 50 sign-ups. That's weird the usernames are random.... Bots! Hmm I probably should have added something like Recaptcha in to validate.</p>
<p><img src="/images/replit-recaptcha.png" alt="Replit recaptcha"></p>
<p>Now the bots have stopped.</p>
<p>Reflecting back on this project, as spent a couple of hours prompting replit and configuring services like Google Cloud and Resend, I noticed that configuration more than I did the prompting, and it hit me that I was building the spec I was going along... My chat log is the development of what I probably should have thought about at the start. I have a strong sense that as these tools develop, while it's mind-blowing that we can use a pithy one-line prompt to generate an entire app, we should be spending a lot more time up front designing the spec so that we are able to get further and more accurate output.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>User Agents Hitting My Site</title>
      <link>https://paul.kinlan.me/user-agents-hitting-my-site/</link>
      <pubDate>Wed, 06 Nov 2024 16:54:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/user-agents-hitting-my-site/</guid>
      <description>Curious about who&#39;s visiting my site, I built a user-agent tracker using Vercel middleware and KV storage.  It logs every request and displays a live table of user agents and hit counts, refreshing every minute. Check out the code on GitHub!</description>
      <content:encoded> <![CDATA[<p>I've always wanted to know which user agents are currently hitting my site... Am I getting crawled? Are ML bots sifting through my site?</p>
<p>I added some middleware to my site to log the user agent of every request while using <a href="https://vercel.com/docs/concepts/kv">Vercel's KV</a> to store the user agents and their counts.</p>
<p>If you are interested in the code, the <a href="https://github.com/PaulKinlan/paul.kinlan.me/blob/main/middleware.ts">middleware</a> and the <a href="https://github.com/PaulKinlan/paul.kinlan.me/blob/main/api/user-agents.ts">api</a> are both on GitHub.</p>
<br>
<h4>User Agents in the last hour</h4>
<table>
<thead>
  <th>User Agent</th>
  <th>Count</th>
  </thead>
  <tbody id="user-agents">
  <tbody>
</table>
<script type="module">
  const render = async (data) => {
    const userAgents = document.getElementById("user-agents");
    userAgents.innerHTML = "";
    const response = await fetch('/api/user-agents.ts');
    const userAgentData = await response.json();

    userAgentData.forEach(item => {
      const row = document.createElement("tr");

      const ua = document.createElement("td");
      const count = document.createElement("td");
      ua.innerText = item[0];
      count.innerText = item[1];
      row.appendChild(ua);
      row.appendChild(count);
      userAgents.appendChild(row);
    });
  };

  render();

  setInterval(() => {
    render();
  }, 60000);
</script>
]]></content:encoded>
    </item>
    
    <item>
      <title>Countdown timer</title>
      <link>https://paul.kinlan.me/projects/countdown-timer/</link>
      <pubDate>Wed, 06 Nov 2024 10:46:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/countdown-timer/</guid>
      <description>I created a simple countdown timer web app that lets you track time until important events.  It&#39;s built with a focus on no-code using Replit, including a cool integration with Black Forrest Labs&#39; image API via Replit&#39;s Agent feature. Check out the live app and source code!</description>
      <content:encoded> <![CDATA[<p>I've a little thing where I like to know roughly how many days it until something. My kids' birthdays, my wedding anniversary, Christmas...</p>
<p>Following my focus on <a href="https://paul.kinlan.me/generated-web-apps/" title="https://paul.kinlan.me/generated-web-apps/">Generated Web Apps</a> where launch sites and services without touching a single line of code, I decided to build a simple service that didn't require anyone to sign-up or sign-in, but could also share the time with their friends and family.</p>
<p><a href="https://countdown-timer.replit.app/" title="https://countdown-timer.replit.app/">https://countdown-timer.replit.app/</a></p>
<p><img src="/images/Screenshot_20241111-115108.jpg" alt="Screenshot\_20241111-115108.jpg">I'm pretty happy with the output. Earlier versions used Dall-e for the background image, but the quality wasn't that good. I replaced it with Black Forrest Labs' API which I think creates far better images. Black Forrest Labs has an API with an OpenAPI spec, but they on don't have any generated client libraries which I thought was going to be an issue. Nope. Replit's Agent could take the OpenAPI spec and directly implement the code to request the image and poll for the response. My mind was kinda blown by this.</p>
<p>The source is here if you are interested: <a href="https://github.com/PaulKinlan/CountdownCrafter" title="https://github.com/PaulKinlan/CountdownCrafter">https://github.com/PaulKinlan/CountdownCrafter</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Will we care about frameworks in the future?</title>
      <link>https://paul.kinlan.me/will-we-care-about-frameworks-in-the-future/</link>
      <pubDate>Mon, 28 Oct 2024 14:54:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/will-we-care-about-frameworks-in-the-future/</guid>
      <description>Building apps with LLMs and agents like Replit has been incredibly productive. The generated code is often vanilla and repetitive, raising questions about the future of frameworks. While frameworks offer abstractions and accelerate development, LLMs seem to disregard these patterns, focusing on implementation. This shift in software development driven by agents may lead to a world where direct code manipulation is unnecessary.  It remains to be seen if frameworks and existing architectural patterns will still be relevant in this LLM-driven future or if new patterns will emerge.</description>
      <content:encoded> <![CDATA[<p><a href="https://paul.kinlan.me/generated-web-apps/" title="https://paul.kinlan.me/generated-web-apps/">I've been building lots of apps by using LLMs and Agents</a>. I'm currently up to about 17 tools, utilities and demos over the duration of a couple of weeks. I build them for me and because tools like Replit have enabled me during my small spots of free time to build fully working sites and apps that solve the immediate problem that I have. I've felt incredibly productive.</p>
<p>I've a hypothesis that something like these Agents will be how we build software in the future, so as I build these apps I try to never adjust the output, I want to see how far that I can get with the machine creating all the code and so see how far away we are from this future.</p>
<p>Replit has been my current tool of choice because while it's a normal IDE, the &quot;Progress&quot; view gives me a diff of all the changes it has made for the current prompt. Because in the most part I know what I am doing I can quickly see if the software has made an error and correct it.</p>
<p>As I watch the code pass by in Replit, I'd noticed that the Python and JavaScript it produced was vanilla and it would frequently duplicate the logic on different screens for the same functionality. It certainly wasn't that type of code that I would craft. I probably would have planned for the removal of repetition. I would have planned for the ability to extend and inherit. I would have planned for testability. Other than the Flask backend, it didn't use any other Frameworks.</p>
<p>Frameworks are abstractions over a platform designed for people and teams to accelerate their teams new work and maintenance while improving the consistency and quality of the projects. They also frequently force a certain type of structure and architecture to your code base. This isn't a bad thing, team productivity is an important aspect of any software.</p>
<p>I'm of the belief that software development is entering a radical shift that is currently driven by agents like Replit's and there is a world where a person never actually has to manipulate code directly anymore. As I was making broad and sweeping changes to the functionality of the applications by throwing the Agent a couple of prompts here and there, the software didn't seem to care that there was repetition in the code across multiple views, it didn't care about shared logic, extensibility or inheritability of components... it just implemented what it needed to do and it did it as vanilla as it could. I was just left wondering if there will be a need for frameworks in the future? Do the architecture patterns we've learnt over the years matter? Will new patterns for software architecture appear that favour LLM management?</p>
<p><a href="https://reddwarf.fandom.com/wiki/RD:_Future_Echoes#:~:text=I%20dunno%2C%20but%20it%27ll%20be%20a%20lot%20of%20fun%20finding%20out!" title="https://reddwarf.fandom.com/wiki/RD:_Future_Echoes#:~:text=I%20dunno%2C%20but%20it%27ll%20be%20a%20lot%20of%20fun%20finding%20out!">I dunno know, but it will be a lot of fun to find out</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>20 years blogging</title>
      <link>https://paul.kinlan.me/20-years-blogging/</link>
      <pubDate>Thu, 03 Oct 2024 10:37:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/20-years-blogging/</guid>
      <description>Wow! Just realized I&#39;ve been blogging for over 20 years, starting way back in August 2004 on kinlan.co.uk with Blogger.  The journey has taken me through Posterous and landed me here on paul.kinlan.me with Hugo (and maybe Jekyll at some point).  Sure, there&#39;s some cringe-worthy stuff in the archives, but it&#39;s &lt;em&gt;my&lt;/em&gt; history.  And honestly, I wouldn&#39;t be where I am today without this little corner of the internet.  Huge thanks to Tim Berners-Lee and everyone who&#39;s made the web what it is!</description>
      <content:encoded> <![CDATA[<p>I missed it, but I just realised that as of August 28th 2024 I've been <a href="https://paul.kinlan.me/first-post" title="https://paul.kinlan.me/first-post">blogging for over 20 year</a>. I would have finished university and left my startups a couple of years before, gone went in to Enterprise, left behind Linux and Perl for a brief fling with c# and .net and started to make my first steps in to being a lot more public both in terms of documenting what I do and helping other folks.</p>
<p>I first started out on <a href="http://kinlan.co.uk" title="http://kinlan.co.uk">kinlan.co.uk</a> which was powered by Blogger and the FTP deploy. I had a brief foray in to posterous (I liked the idea of email publishing), and then settled at some point on <a href="http://paul.kinlan.me" title="http://paul.kinlan.me">paul.kinlan.me</a> and Hugo (I think i also used Jekyll at some point).</p>
<p>Looking back through my archives, I've written some really rubbish things.... I was alway terrible at spelling and grammar. But they're my things, and I like that.</p>
<p>I can also say that I wouldn't be doing what I do today without my little place on the Internet.</p>
<p>The web is my home and I hope I can help it stay everyone's home in the future. I'm thankful to Sir Tim Berners-Lee for inventing this thing and all the people that have built on top of this platform to make it what it is today (warts and all).</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Generated Web Apps</title>
      <link>https://paul.kinlan.me/generated-web-apps/</link>
      <pubDate>Mon, 23 Sep 2024 07:53:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/generated-web-apps/</guid>
      <description>This blog post lists various web apps I&#39;ve generated using Repl.it and WebSim,</description>
      <content:encoded> <![CDATA[<p>Following on from my post about the &quot;<a href="https://paul.kinlan.me/the-disposable-web/">disposable web</a>&quot; and building things just for me, I thought it might be useful to collate an evergreen list of all the things that I'm building (and their code) so that you can see some of the things they do and inspect the code that is produced (I am expecting that there are issues and if you spot any, it would be good to highlight them)</p>
<p>For as much as possible, I try not to change any of the code.</p>
<p><a href="http://Repl.it"><strong>Repl.it</strong></a></p>
<ul>
<li>
<p><a href="https://image-analyzer-paulkinlan.replit.app/">Image Analyzer</a> [<a href="https://github.com/PaulKinlan/imageanalyzer/commits/main/">code</a>] - I sometimes need to extract information from images and I just wanted a tool I trust (and also see how well it works with Google Cloud APIs - quite well)</p>
</li>
<li>
<p><a href="https://replit.com/@paulkinlan/TimeZoneTracker">TimeZone Tracker</a> [<a href="https://github.com/PaulKinlan/TimezoneTracker">code</a>] - This is simple tool that lets me set up time zones so that I can plan meetings. There are sometimes that I had to step into intervene, like when I asked it to make it a PWA and it kept auto generating the icons (I had my own), or when I couldn't get the log into persist (turns out it was the WebView iframe)</p>
</li>
<li>
<p><a href="https://rude-check-app-paulkinlan.replit.app/">Rude name checker</a> - I've launched products that have names that might mean something rude in another language. I just wanted to see if I could build something simple that might help people. I still remember launching <a href="https://thenextweb.com/news/wanted-micropodcast-friendfeed-friendboo">FriendBoo</a> and being told that in some languages Boo is the sound of a fart!</p>
</li>
<li>
<p><a href="https://image-gen-paulkinlan.replit.app/">Image Generator from Prompt in a URL</a> - I really like placekitten, where you can just put in url and get an image. I hadn't seen anyone create a thing where you include the URL + prompt and get an image e.g, <a href="https://image-gen-paulkinlan.replit.app/generate/a%20beautiful%20landscape%20with%20mountains%20and%20a%20lake">https://image-gen-paulkinlan.replit.app/generate/a+beautiful+landscape+with+mountains+and+a+lake</a> (presumably, because it costs money)</p>
</li>
<li>
<p><a href="https://pressure-tracker-paulkinlan.replit.app/">BloodPressureTracker</a> [<a href="https://github.com/PaulKinlan/BloodPressureTracker">code</a>] - A recent health scare has made me need/want to track my blood pressure. I'm not ready to use a full app yet, so this simple interface works for me. It's more robust than a spreadsheet.</p>
</li>
<li>
<p><a href="https://github.com/PaulKinlan/MediaTrackerChromeExtension">Media Logger Extension</a> - This is a Chrome Extension that records when the user navigates to a page with Audio or Video media and allows you to go back to it and interact with it. I'm a visual and aural person so I wrote this because I frequently want a simple way to quickly discover embedded podcasts or videos and quickly get back to it.</p>
</li>
<li>
<p><a href="https://critic-app.replit.app/">The Critic v2</a> [<a href="https://github.com/PaulKinlan/CriticApp">code</a>] - I converted my original &quot;The Critic&quot; that was written with Breadboard to one that was built entirely by the Replit Agent. It's a lot more comprehensive, it includes account management, templates and histories.</p>
</li>
<li>
<p><a href="https://countdown-timer.replit.app/">Countdown Timer</a> [<a href="https://github.com/PaulKinlan/CountdownCrafter">code</a>] - I'm taking my parents on a trip to Japan and I wanted a simple home screen installable web app that lets me configure a countdown timer and share it quickly and easily.</p>
</li>
<li>
<p><a href="http://tldr.express">tldr.express</a> - Daily RSS summaries</p>
</li>
<li>
<p><a href="http://SendVia.me">SendVia.me</a> [<a href="https://github.com/PaulKinlan/send-to-remarkable">code</a>]- A service that allows you to send PDF's directly to your reMarkable</p>
</li>
<li>
<p><a href="https://blog-craft-editor-paulkinlan.replit.app/">blog-craft-editor-paulkinlan.replit.app</a> [<a href="https://github.com/PaulKinlan/BlogCraftEditor">code</a>]- a simple UI for editing my Hugo based Blog.</p>
</li>
</ul>
<p>A general note, <a href="http://repl.it">repl.it</a> seems to prefer Python, Flask and sqlite. It's not the end of the world with regard to Python and Flask, but I don't quite understand why it prefers sqlite, as I understand it, every time you deploy it will overwrite the sqlite file, which seems sub-optimal. I prefer to use postgres and I've been delighted with the agents ability to migrate databases.</p>
<p><a href="https://websim.ai"><strong>WebSim</strong></a></p>
<p>I really like <a href="https://paul.kinlan.me/fictitious-web/">Web Sim for just spending time browsing around things</a>. I think it highlights the power of the web, in that anyone can create something interactive and just publish out there.</p>
<ul>
<li>
<p><a href="https://websim.ai/@paul_kinlan/3d-rotating-globe-with-country-outlines-city-popul">3d globe</a> - One of our first webgl demos was a spinning globe with pins on it. I wanted to see if it could do this.</p>
</li>
<li>
<p><a href="https://websim.ai/@paul_kinlan/discover-toya-a-cultural-journey-in-hokkaido">Lake Toya</a> - I was showing my wife how easy it would be for us to create a site about a recent trip that we had.</p>
</li>
<li>
<p><a href="https://websim.ai/@paul_kinlan/2d-gravity-simulator-with-infinite-space-2">2d Space Gravity Simulator</a> - Something that I always wanted to build but never had the time.</p>
</li>
<li>
<p><a href="https://websim.ai/@paul_kinlan/3d-gravity-simulation-with-pan-controls">3d Space Gravity Simulator</a> - I wanted to see what o1-preview could do, it was based on the 2d version.</p>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>The disposable web</title>
      <link>https://paul.kinlan.me/the-disposable-web/</link>
      <pubDate>Wed, 11 Sep 2024 19:49:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/the-disposable-web/</guid>
      <description>Reflecting on my journey with computers, from the C64 and Amiga 500 to the present day, I&#39;ve found a renewed excitement in software development.  New tools like repl.it and websim.ai empower rapid creation of full-stack, disposable web apps – software built for personal use and easily discarded. This ease of creation removes the barrier to starting projects, making the web an ideal platform for even single-user applications. It&#39;s a shift from handcrafted software to a more ephemeral approach, allowing for quicker prototyping and experimentation.</description>
      <content:encoded> <![CDATA[<p>I have vivid memories as a child of my dad buying an Amiga 500 and letting me have exclusive access to the C64. I'd plugin in cassettes and 5 1/4 inch disks and play California games. One day I was in the local newsagent and I saw a comic about computers. On the front of the comic it had two little horned devil things called Rom and Ram. I have visceral memories of the smell of that comic, the paper, the cigarette smoke (the newsagent owners puffed like chimneys and would spend most of their money on their own supply of cigarettes) and in between me seemingly inhaling the magazine, I also devoured the contents. I could type in the listings and after much debugging and re-typing play the game.</p>
<p>My dad saw this and suggested that I actually learn to program, in fact I should look at loops and variables... I had no clue what he was talking about.</p>
<p>The magazine ended and a while later I got access to the Amiga 500 because my dad upgraded to a 386DX66. With access to this Amiga, not only could I &quot;swap&quot; disks with enthusiast in the Birkenhead computer club, I could see that people were making demos at the start of these disks... Many of these disks had the name &quot;Pirate Pete&quot; bouncing around the screen in ways that I'd never seen before. I can do that! and before long I found a little window in Workbench that said &quot;Basic&quot; and much like today, I thought maybe if I just mash my hands in style on the keyboard I would get a game out of the back of it.</p>
<p>I don't know why, but it didn't work. But by then Amiga was not much in my mind, I saw a new demo called DOOM and it looked awesome.</p>
<p>At the same time in high-school, our advanced suite of BBCs and Nimbus's (Nimbusi?) didn't fulfil me. I could see some kids writing BASIC to change the sides of the screens, and my friend Bob had written his magnum opus: &quot;Bird pooh's&quot;, and I still didn't get it.</p>
<p>One eventful day on a school trip in Chester, by the River Dee there was an arcade where I saw Streetfighter II, and things clicked. Movements of the joystick and buttons would change the state of the program and the graphics. I'd have to loop around a lot of times to keep thing going... Hey, I knew how DOOM worked too...!</p>
<p>By fortune, my grandfather was a tinkerer. Heswall Computers sold him a top of the line 286 (don't ask...) that had QBasic on. My grandad had a problem. He couldn't pick his National Lottery numbers, and so was born my first actual computer program (btw - I nearly quit because I had no clue our chums in America spelt colour incorrectly).</p>
<p>From then until now I've spent huge amounts of time building software for businesses and myself. I tinker. I learn. Software let's me do mostly whatever I want.</p>
<p>What does this all have to do with &quot;the disposable web&quot; I hear you ask?</p>
<p>A lot of the software I've written in my life has been for me and only me. While I've improved my skills a lot of these things still take quite a lot of time to build and so there is always this thing that stops me starting.</p>
<p>Well, I'm back to mashing my hands on the keyboard but this time actually getting things out of it. I wrote the other day about <a href="https://paul.kinlan.me/fictitious-web/" title="https://paul.kinlan.me/fictitious-web/">websim</a><a href="http://websim.ai" title="http://websim.ai">.ai</a> and this week I've been playing with <a href="http://repl.it" title="http://repl.it">repl.it</a>'s <a href="https://docs.replit.com/replitai/agent" title="https://docs.replit.com/replitai/agent">agent</a> and I was able to build a full-stack application that is a PWA, with a UI, authentication, a Python back-end and a Postgres database that I can deploy with one-click, all in about one hour.</p>
<p>Scott Jenson and I were exchanging a chat on <a href="https://social.coop/@scottjenson/113109865228412874" title="https://social.coop/@scottjenson/113109865228412874">mastodon</a> and he said the exact same thing that I was thinking:</p>
<blockquote>
<p>&quot;<a href="https://status.kinlan.me/@paul" title="https://status.kinlan.me/@paul">@paul</a> that's what I'm using llms for these days. I'm actually writing all sorts of little web pages that I could do myself but I just wouldn't bother. They're just so easy to put together in minutes and then just throw them away.&quot;</p>
</blockquote>
<p>Tools like <a href="http://websim.ai" title="http://websim.ai">websim.ai</a> and <a href="http://repl.it" title="http://repl.it">repl.it</a> are enabling me to create the things that I need and I've always wanted but didn't feel that I had the time to do... But what happens to that software? Do I scale it? I don't necessarily want other people to use the tools, they're really just meant for me. Maybe they will be useful.... It's not something that I always want to think about.</p>
<p>When I hand make something and put time into it, I have an attachment to it. I don't want to lose it. Maybe it's a sunk-cost fallacy thing, or just my ego... When I prompt it.... It doesn't matter, it's disposable.</p>
<p>I really believe that we're at a point where software can be disposable. You can now write once, run <del>everywhere</del> once, and that's ok. And for me the web is the best platform to do create and run software on even if it is only just for me.</p>
<p>This new tooling is stopping me from stopping starting.</p>
<p><br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
ps - I know there's a lot of preamble, but that history of Paul has been on my mind for a while and I wanted to get it out.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>I spent an evening on a fictitious web</title>
      <link>https://paul.kinlan.me/fictitious-web/</link>
      <pubDate>Wed, 28 Aug 2024 14:44:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/fictitious-web/</guid>
      <description>Experimented with WebSim, a simulated web environment, creating sites like a personal blog, timezone converter, interactive globe, and a travel site. The experience was reminiscent of the early web&#39;s playful exploration and highlighted WebSim&#39;s potential for creativity and interactive experiences.</description>
      <content:encoded> <![CDATA[<p>I saw <a href="https://websim.ai">https://websim.ai</a> a couple of weeks ago but didn't quite get it, and then during some research on the creator and web developer ecosystems I came back to it and my mind was blown.</p>
<p>I spent the evening exploring a web that is full of applications and sites and only limited by URLs that I could think of (heh - this web never has a 404 or an unregistered domain). WebSim provides a simulation of the web via a faux Web Browser. It's a web that doesn't actually exist, but one that works incredibly well. I obviously went to <a href="https://websim.ai/@paul_kinlan/paul-kinlan-s-blog">my site first</a> (note - I re-did this when I published it).</p>
<p><img src="/images/websim-paulkinlan-me.png" alt="paul.kinlan.me on websim"></p>
<p>I then created a clone of <a href="https://websim.ai/@paul_kinlan/everytimezone-com">everytimezone</a> (I needed to arrange a meeting in the NYC and Mountain View)</p>
<p><img src="/images/websim-everytimezone.png" alt="everytimezone on websim"></p>
<p>And then I built an interactive globe showing population of <em>some</em> major cities</p>
<p><img src="/images/websim-globe.png" alt="3d globe on websim"></p>
<p>And then I wanted to see if I could create a <a href="https://websim.ai/@paul_kinlan/discover-toya-a-cultural-journey-in-hokkaido">simple site for a trip to Toya in Hokkaido</a></p>
<p><img src="/images/websim-hokkaido.png" alt="Toya on websim"></p>
<p>My wife and I created a site that showcases the beauty of North Wales to a Japanese audience (we didn't publish it)... and I just explored and explored. I played with simple games, interactive experiences, I even got on a plane.... It's like the Web's Roblox.</p>
<p>The entire evening reminded me of when I first discovered blogging and would just go from site to site, reading and playing with the things people built and just having a lot of fun. On one hand this was demonstrates an entire virtual world that I just got lost for hours in this new virtual world, on the other the fact that entire sites and functioning games and apps work is just mind blowing.</p>
<p>I really do encourage everyone to try and play with <a href="https://websim.ai">https://websim.ai</a> even if it takes a little while to get the hang of the <a href="https://websim.ai/@katwinter/websim-ui-guide">UI for generating sites</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Idly musing about Manifest</title>
      <link>https://paul.kinlan.me/idly-musing-about-manifest/</link>
      <pubDate>Wed, 21 Aug 2024 17:25:01 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/idly-musing-about-manifest/</guid>
      <description>In this blog post, I share some findings from my exploration of HTTP Archive data.  I discovered that a significant number of websites using manifest.json files are using the default configuration generated by Create React App.  I plan to investigate this further and determine how prevalent default manifest files are across the web.</description>
      <content:encoded> <![CDATA[<p>I sometimes go for a spelunk in to HTTP Archive looking at values here and there. I thought I would look at default manifest.json values.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#a6e22e">SELECT</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">COUNT</span>(<span style="color:#f92672">*</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">FROM</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">`httparchive.response_bodies.2024_08_01_desktop`</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WHERE</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">url</span> <span style="color:#a6e22e">LIKE</span> <span style="color:#e6db74">&#39;%manifest.json&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AND</span> <span style="color:#a6e22e">response_body</span> <span style="color:#a6e22e">LIKE</span> <span style="color:#e6db74">&#39;%&#34;short_name&#34;: &#34;React App&#34;%&#39;</span>
</span></span></code></pre></div><p>There are 551037 urls that end 'manifest.json' and 22303 of those manifest files have the default manifest file generated by Create React App.</p>
<p>It's probably worth exploring how many manifests are the default manifest created by build tooling.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Reactive Agents</title>
      <link>https://paul.kinlan.me/projects/reactive-agents/</link>
      <pubDate>Wed, 21 Aug 2024 13:42:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/reactive-agents/</guid>
      <description>I&#39;m exploring a new way to build reactive applications using an &#39;Agents&#39; API.  Inspired by Preact Signals and my previous reactive-prompt project, this toolkit uses Chrome&#39;s prompt API.  Each Agent has a persona, task, and context, reacting to input changes.  You can chain Agents, passing data between them.  I&#39;ve created different Agent types like a &#39;Human&#39; Agent representing user input and a &#39;ToolCaller&#39; that can execute JavaScript functions based on context.  This experiment explores data-flow-driven LLM applications, similar to Breadboard, and leverages Preact Signals for managing this flow.</description>
      <content:encoded> <![CDATA[<p>I've been pretty enamoured by <a href="https://preactjs.com/guide/v10/signals/">Preact's Signals API</a> and how it makes it easy to build applications that respond to state and environment changes, so following on from the <a href="https://paul.kinlan.me/projects/reactive-prompts/">reactive-prompt</a> API that I built the other month, I've been exploring a higher-level <code>Agents</code> API that follows the same principles: Agents that can react to their environment using the Signals API.</p>
<p>An Agent is a tool that given an input and a way of working will try to perform all the actions needed to complete the task. <a href="https://github.com/paulkinlan/reactive-agent"><code>reactive-agent</code></a> is my first attempt at a toolkit for building browser-based applications with Agents using Chrome's prompt API.</p>
<p>The general gist is that each &quot;Agent&quot; has a persona (How it should think or act), a task (What the user needs it to do) and a context (That data that it should work on). Once it is supplied with data it should try to run to complete the task it was given.</p>
<p>In this framework, each Agent will only run (or react) when their input changes. This allows you to chain the output of one Agent into the input of another and build complex chains of agents that might only partially update as the state of the application changes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">effect</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@preact/signals-core&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Agent</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@paulkinlan/reactive-agent&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">interviewer</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">persona</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a pirate and you speak like a pirate&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">task</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You need to find out how old the user is&#34;</span>
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">interview</span>.<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Paul Kinlan&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">effect</span>(() =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// This is where the agent starts to get resolved.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">interviewer</span>.<span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>This Agent isn't particularly useful, all it can do is come up with a question. So once the agent has decided on a question, you need to get an answer from the user. This is where the <code>Human</code> Agent comes in. In the code below we are connecting the input <code>context</code> of the <code>Human</code> to the output of the Pirate Agent.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">effect</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@preact/signals-core&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Agent</span>, <span style="color:#a6e22e">Human</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@paulkinlan/reactive-agent&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">agent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">persona</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a pirate and you speak like a pirate&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">task</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You need to find out how old the user is&#34;</span>
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">human</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Human</span>({});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">effect</span>(() =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">human</span>.<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">agent</span>.<span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">value</span>;
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">effect</span>(() =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">human</span>.<span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">agent</span>.<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Paul Kinlan&#34;</span>;
</span></span></code></pre></div><p>The <code>Human</code> agent is a representation of the user in the flow of data through the Agent. The more important part of this is we are chaining the output of one agent into another.</p>
<p>A more interesting Agent would be one that perform actions, or more specifically can call existing JS functions. A Tool calling agent can work out which function to call and what parameters to provide based on the input context.</p>
<p>In this case, the persona and task are pre-configured and can't be changed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">effect</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@preact/signals-core&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">ToolCaller</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@paulkinlan/reactive-agent&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">toolCaller</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">ToolCaller</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">func</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getWeather</span>(<span style="color:#a6e22e">location</span>) { <span style="color:#66d9ef">return</span> <span style="color:#e6db74">`It&#39;s Hot in </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">location</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>; },
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Get the weather for a given location&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">func</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getTime</span>(<span style="color:#a6e22e">location</span>) { <span style="color:#66d9ef">return</span> Date.<span style="color:#a6e22e">now</span>(); },
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Get the time for a given location&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">effect</span>(()=&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Log the result of the function call.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">toolCaller</span>.<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">toolCaller</span>.<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;What is the weather in London?&#34;</span>;
</span></span></code></pre></div><p>This project is just an experiment, and like with <a href="https://github.com/breadboard-ai/breadboard">Breadboard</a>, I love the idea of describing LLM based applications as graphs of data-flow. This might not be the way to build applications in the future, but it feels pretty powerful. I also think that Preact's Signals (well, Signals in general) are an incredibly powerful mechanism for managing data flow in an application and they don't have to be used just for updating UI.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Reactive Prompts</title>
      <link>https://paul.kinlan.me/projects/reactive-prompts/</link>
      <pubDate>Fri, 07 Jun 2024 13:52:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/reactive-prompts/</guid>
      <description>I&#39;ve created a small library called &lt;code&gt;reactive-prompt&lt;/code&gt; that lets you easily manage prompts in a reactive way, similar to how you&#39;d build a web app with React. It uses Preact&#39;s Signals to track changes to inputs and automatically re-runs prompts when those inputs update.  This allows for efficient chaining of prompts, where the output of one becomes the input of another, and only necessary prompts are re-evaluated. The library currently uses Chrome&#39;s experimental prompt API but could be adapted for other providers like OpenAI or Gemini.  It makes complex prompt flows much more manageable.</description>
      <content:encoded> <![CDATA[<p>I've been doing a lot of work on <a href="https://github.com/breadboard-ai/breadboard">Breadboard</a>. Breadboard is great because it changes the way you think about data flow through an LLM application by focusing on thinking about graphs. One of the things it does well is reacting to inputs updating and chaining of prompts. I wanted to see if I can make a simple imperative way to react to changing inputs.</p>
<p>I thought it would be neat to experiment with:</p>
<ol>
<li>Prompt responses should rerun (aka react) as the inputs to the prompt change (like a Web App with React)</li>
<li>You should be able to chain responses easily, that is the output of one response can be used in another prompt.</li>
<li>It should integrate with Chrome's (very experimental)</li>
</ol>
<p>Preact's <code>Signals</code> seemed like an ideal way to manage this.</p>
<p>The <a href="https://www.npmjs.com/package/@paulkinlan/reactive-prompt"><code>reactive-prompt</code></a> library exports a single function called <code>prompt</code>. It's a tagged template literal which allows you to substitute any variable including Signals. When a Signal is referenced and updated, it's prompt will be recalculated.... and now we have reactive prompts.</p>
<h3 id="simple-demo">Simple Demo</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">prompt</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@paulkinlan/reactive-prompt&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">signal</span>, <span style="color:#a6e22e">effect</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@preact/signals-core&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">signal</span>(<span style="color:#e6db74">&#34;Paul&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">response</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">prompt</span><span style="color:#e6db74">`Say &#34;Hello </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">name</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;.`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">effect</span>(<span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">value</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">setTimeout</span>(() =&gt; <span style="color:#a6e22e">name</span>.<span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Serene&#34;</span>, <span style="color:#ae81ff">2000</span>);
</span></span></code></pre></div><h3 id="prompt-chaining">Prompt Chaining</h3>
<p>You can chain multiple prompts together, that is the output of one prompt can be the input of another prompt. For sufficiently complex data flows this means that you only the prompts that need updating will be re-run.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">prompt</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@paulkinlan/reactive-prompt&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">signal</span>, <span style="color:#a6e22e">effect</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@preact/signals-core&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">nameSignal</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">signal</span>(<span style="color:#e6db74">&#34;Paul Kinlan&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">prompterSignal</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">prompt</span><span style="color:#e6db74">`Using &#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">nameSignal</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;, extract the following data:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">+ First name
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">+ Surname
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">+ Date of Birth
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Return as valid JSON
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">uiBuilderSignal</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">prompt</span><span style="color:#e6db74">`You are an expert web developer, and you have been tasked with creating a form for a client. The form should have the following fields: &#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">prompterSignal</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Return the required HTML for the form only and populate the default values.`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">effect</span>(<span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">output</span>.<span style="color:#a6e22e">innerHTML</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">parseCodeFromMarkdown</span>(<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">uiBuilderSignal</span>.<span style="color:#a6e22e">value</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">setTimeout</span>(() =&gt; <span style="color:#a6e22e">name</span>.<span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Jack Jones, 18th May 1967&#34;</span>, <span style="color:#ae81ff">5000</span>);
</span></span></code></pre></div><h2 id="chromes-experimental-prompt-api">Chrome's experimental prompt API</h2>
<p>This library relies on Chrome's experimental prompt API.</p>
<p>To use this, you need at least Chrome 127 (Dev Channel) and to enable the following flags.</p>
<ul>
<li>chrome://flags/#prompt-api-for-gemini-nano</li>
<li>chrome://flags/#optimization-guide-on-device-model &quot;Enable Bypass&quot;</li>
</ul>
<p>Then go to <code>chrome://components</code> and click &quot;Check for updates&quot; on &quot;Optimization Guide On Device Model&quot;. After some time the model will be available.</p>
<h2 id="what-about-other-prompt-apis">What about other prompt APIs?</h2>
<p>The <code>prompt</code> tagged template is just built for Chrome's prompt API, but there is nothing stopping this being being ported to other APIs like OpenAI or Gemini's REST APIs.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>transformerjs-breadboard-kit</title>
      <link>https://paul.kinlan.me/projects/transformerjs-breadboard-kit/</link>
      <pubDate>Fri, 23 Feb 2024 07:45:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/transformerjs-breadboard-kit/</guid>
      <description>I&#39;ve created a new Breadboard kit for TransformerJS, which allows you to run HuggingFace models locally on devices.  This is useful for LLM-related tasks where cloud access isn&#39;t ideal.  The kit is available on GitHub (&lt;a href=&#34;https://github.com/PaulKinlan/transformerjs-breadboard-kit&#34;&gt;https://github.com/PaulKinlan/transformerjs-breadboard-kit&lt;/a&gt;) and can be installed via npm with &lt;code&gt;npm i @paulkinlan/transformerjs-breadboard-kit&lt;/code&gt;.  Included is an example of creating a Breadboard agent for text summarization.</description>
      <content:encoded> <![CDATA[<p>A lot of the tools that I build that interact with LLMs are built with Breadboard. I needed to be able to run a number of tasks on the device that I have the board without using an LLM in the cloud. <a href="https://huggingface.co/docs/transformers.js/en/index" title="https://huggingface.co/docs/transformers.js/en/index">TransformerJS</a> is a great API for JS developers to work with a number of different models that are hosted on HuggingFace. To get this working with breadboard I needed to create a Kit.... so here we are.</p>
<p>You can find the <a href="https://github.com/PaulKinlan/transformerjs-breadboard-kit" title="https://github.com/PaulKinlan/transformerjs-breadboard-kit">code here</a> and if you are using breadboard you can install it as <code>npm i @paulkinlan/transformerjs-breadboard-kit</code></p>
<p>To create an Breadboard agent you can do it as follows:</p>
<pre tabindex="0"><code>import { board } from &#34;@google-labs/breadboard&#34;;
import { sentiment } from &#34;@paulkinlan/transformerjs-breadboard-kit&#34;

export default await board(({ text, model }) =&gt; {
    text.isString();
    model.isString();

    const summarynode = transformersjs.summarize({
      $id: &#34;summary&#34;,
      input: text,
      model,
    });

    return summarynode.to(base.output());
  }).serialize({
  title: &#34;Summary board&#34;,
  description: &#34;Runs the summarization analysis with transformer JS&#34;,
  version: &#34;0.0.2&#34;,
});
</code></pre>]]></content:encoded>
    </item>
    
    <item>
      <title>The Critic</title>
      <link>https://paul.kinlan.me/projects/the-critic/</link>
      <pubDate>Tue, 20 Feb 2024 11:18:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/the-critic/</guid>
      <description>I created &amp;quot;The Critic&amp;quot;, a tool to help review text from different perspectives, like accessibility or API design. It uses LLMs to simulate multiple &amp;quot;agents&amp;quot; who provide feedback, streamlining the review process.  It&#39;s built with Breadboard and aims to enhance, not replace, human review.  While the UI is basic, it effectively identifies potential improvement areas, sparks discussions, and integrates with LLMs for convenience.</description>
      <content:encoded> <![CDATA[<p>I'm broadly interested in Large Language Models and how they can help Developer Relations (my role).</p>
<p>I spend a lot of time reading proposals for new APIs and initiatives and then providing feedback to the authors to help them make sure that they work well for the ecosystem. A lot of the feedback follows a pattern of &quot;Have you thought about X?&quot; (in many cases people have, but it's just not mentioned clearly in the text)</p>
<p>I wanted a tool that would help me critique articles, Web specs and API explainers from a variety of different perspectives and help me to provide feedback to the teams writing them so that they can be improved.</p>
<p><a href="https://violet-afraid-quotes-paulkinlan.replit.app/" title="https://violet-afraid-quotes-paulkinlan.replit.app/">The Critic</a> is a basic UI that lets you configure a number of &quot;agents&quot; who take on a persona whose job it is to read the input text and provide a generated &quot;interpretation&quot; of the text with areas that should be addressed.</p>
<p>It's also not doing anything that you can't do with an LLM already (i.e, adding preamble to your question like &quot;You are an expert in Web Accessibility&quot;), but it adds a small layer of convenience over an LLMs UI by making the requests for each expert agent at the same time and then creating a report at the end (note, the full report is in the console, but each Agent will have a response).</p>
<p>The UI is very rough, but the tool works pretty well. It's built using <a href="https://github.com/breadboard-ai/breadboard" title="https://github.com/breadboard-ai/breadboard">Breadboard</a> (again :)) where a Critic is <a href="https://github.com/PaulKinlan/TheCritic/blob/main/ui-src/boards/critic.ts" title="https://github.com/PaulKinlan/TheCritic/blob/main/ui-src/boards/critic.ts">defined as a board</a> and the <a href="https://github.com/PaulKinlan/TheCritic/blob/main/ui-src/boards/the-panel.ts#L49" title="https://github.com/PaulKinlan/TheCritic/blob/main/ui-src/boards/the-panel.ts#L49">Panel board</a> will <code>map</code> the list of inputs against each critic and article pair.</p>
<p>At the end of the day, it's an LLM, it doesn't replace what people are supposed to do or absolve the authors from thinking about the critical areas up front, and likewise, just because an LLM doesn't reply with something doesn't mean the input is perfect either. I found it useful for starting conversations though.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>tldr-site.vercel.app</title>
      <link>https://paul.kinlan.me/projects/tldr-site/</link>
      <pubDate>Tue, 20 Feb 2024 10:59:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/tldr-site/</guid>
      <description>I built tldr-site.vercel.app to help me quickly understand web development trends. It summarizes search results and news snippets, giving me a high-level overview before I dive into specific details.  I use it frequently, for example, to research community feedback like on the Web Environment Integrity API. The code is on GitHub and uses Breadboard to manage data flow. It works well, but could be improved by adding article content for richer context.</description>
      <content:encoded> <![CDATA[<p>As part of my role in Developer Relations for Chrome I need to get a quick understanding of what's happening around web development broadly, and also for specific areas of interest for the team.</p>
<p>In August 2023 I built this &quot;tldr-site&quot; as an experiment to see if I can get useful information out of the summaries provided by search and news feeds, with the goal of giving me a high-level overview so that I can then go and deep dive into the actual results.</p>
<ul>
<li>
<p><a href="https://tldr-site.vercel.app/" title="https://tldr-site.vercel.app/">Summarize a search</a></p>
</li>
<li>
<p><a href="https://tldr-site.vercel.app/news" title="https://tldr-site.vercel.app/news">Summarize the News</a></p>
</li>
</ul>
<p>I use this quite frequently, for example I needed to get a quick overview of what people thought of the Web Environment Integrity API so that I could make some recomendations internally.</p>
<p>The <a href="https://github.com/paulkinlan/tldr.site" title="https://github.com/paulkinlan/tldr.site">Code</a> uses Breadboard as the engine to manage the flow of data through the query to the Google Search API or the News feeds and then on to the LLM.</p>
<p>It works pretty well, but clearly it doesn't have enough context because the snippets in search and news are very limited. A future iteration of this would go and fetch the articles in returned for each result, add that to the context and summarize the results.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Some clean-up new-year</title>
      <link>https://paul.kinlan.me/some-cleanup/</link>
      <pubDate>Tue, 20 Feb 2024 10:54:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/some-cleanup/</guid>
      <description>I&#39;ve made a couple of small changes to the blog. I removed the personal journal section and added my projects to the RSS feed so you can see what I&#39;ve been working on with Generative AI. Happy New Year!</description>
      <content:encoded> <![CDATA[<p>There's not that many people who read this blog - but for those of you who do, I'm making some minor changes.</p>
<ul>
<li>
<p>Removed the journal - I stopped personal journalling in the middle of the year (I still do it for work) - I don't think there was a huge amount of value in the bulleted lists for everyone else, so I've removed it from the blog.</p>
</li>
<li>
<p>Added <a href="https://paul.kinlan.me/projects/" title="https://paul.kinlan.me/projects/">projects</a> to the RSS feed. I've had a pretty productive end of 2023 and I did a lot of work related to Generative AI, and I'd like to share them a little more broadly.</p>
</li>
</ul>
<p>... Also, Happy New Year, while I have you reading this.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Claude Breadboard Kit</title>
      <link>https://paul.kinlan.me/projects/claude-breadboard-kit/</link>
      <pubDate>Tue, 20 Feb 2024 10:38:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/claude-breadboard-kit/</guid>
      <description>I&#39;ve created the Claude Breadboard Kit, a simple plugin to easily connect your Breadboards with the Claude API.  It features a single &lt;code&gt;generateCompletion&lt;/code&gt; node for streamlined integration. Add it to your breadboard runtime using &lt;code&gt;addRuntimeKit(Claude)&lt;/code&gt; and start using the  &lt;code&gt;generateCompletion&lt;/code&gt; node.</description>
      <content:encoded> <![CDATA[<p>The <a href="https://github.com/PaulKinlan/claude-breadboard-kit" title="https://github.com/PaulKinlan/claude-breadboard-kit">Claude Breadboard Kit</a> is a simple plugin that enables you to build Breadboards that interface directly with Claude.</p>
<p>It offers just one node: <code>generateCompletion</code>.</p>
<p>You can integrate it into your breadboard runtime by adding it as a kit using <code>addRuntimeKit(Claude)</code> and then reference the <code>generateCompletion</code> node.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>tldr.rocks</title>
      <link>https://paul.kinlan.me/projects/tldr-rocks/</link>
      <pubDate>Tue, 20 Feb 2024 10:12:00 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/projects/tldr-rocks/</guid>
      <description>I created tldr.rocks, a simple site that summarizes Hacker News posts and comment sentiment.  As a DevRel, it&#39;s crucial to understand developer sentiment towards our work. This tool helps me quickly identify key comments and critical challenges, although it doesn&#39;t replace direct engagement with the community.  It&#39;s built with Hugo, uses Breadboard to manage the Generative AI, and leverages Anthropic&#39;s Claude via my &amp;quot;Claude Kit&amp;quot; Breadboard integration.</description>
      <content:encoded> <![CDATA[<p><a href="http://tldr.rocks" title="http://tldr.rocks">tldr.rocks</a> [<a href="https://github.com/PaulKinlan/tldr.rocks" title="https://github.com/PaulKinlan/tldr.rocks">code</a>] is a simple service that I created to summarize the Hacker News posts and the sentiment of the comments.</p>
<p>I built this tool because as part of a DevRel team it's important to understand how the people that we work with (developers) feel about our work and the platform as a whole. It can take a lot of time to go through each of the comments to understand what the issues are, so I built this tool to help me find the most important comments.</p>
<p>This tool isn't a replacement for spending time in the ecosystem and understanding needs, but helps to identify critical challenges and how teams might address them.</p>
<p>The UI is a simple Hugo static CMS. The Generative AI is managed by <a href="https://github.com/breadboard-ai/breadboard" title="https://github.com/breadboard-ai/breadboard">Breadboard</a> (a project that I work on) and Anthropic's <a href="https://docs.anthropic.com/claude/reference/getting-started-with-the-api" title="https://docs.anthropic.com/claude/reference/getting-started-with-the-api">Claude</a> using the Breadboard &quot;<a href="https://github.com/PaulKinlan/claude-breadboard-kit" title="https://github.com/PaulKinlan/claude-breadboard-kit">Claude Kit</a>&quot;.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Chat GPT Code Interpreter and Browser Compat Data</title>
      <link>https://paul.kinlan.me/chatgpt-code-interpreter-and-browser-compat-data/</link>
      <pubDate>Tue, 11 Jul 2023 11:51:48 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/chatgpt-code-interpreter-and-browser-compat-data/</guid>
      <description>I explored using ChatGPT&#39;s Code Interpreter to analyze browser compatibility data from the BCD project.  My goal was to determine the latest released versions of different browsers.  While the initial results weren&#39;t perfect, through a few iterations of feedback, the Code Interpreter generated a Python script that accurately extracted the desired information.  I was impressed by the speed and efficiency of this process, as it accomplished in minutes what would have taken me much longer manually.  The generated code also provided a starting point for further analysis, like visualizing browser release timelines.  Despite minor imperfections, the Code Interpreter proved to be a powerful tool for quickly extracting and analyzing data.</description>
      <content:encoded> <![CDATA[<p>One of the problems that I have with LLMs is knowing when they will be useful and how to apply them to any given problem. A lot of it just feels alien to me because with a background in computer programing I've been trained over 30 years that we frequently will get a deterministic set of results.</p>
<p>I like to experiment and break new mental ground, so when I saw that Chat GPT had a code interpreter, I was was interested and yet had no clue what I would do with it.</p>
<p>I had a bit of a think, and my <a href="https://paul.kinlan.me/bcd-a-hidden-web-compat-gem/">side-passion is querying the BCD</a> (<a href="https://github.com/mdn/browser-compat-data">Browser Compat Data</a>) project for interesting information. BCD is a repository of features that are available to web browsers and their availability in them, for example: CSS Grid and Flexbox support.</p>
<p>The BCD project is accessible as a <a href="https://unpkg.com/@mdn/browser-compat-data/data.json">large JSON file</a> and in many previous projects I create scripts or websites to parse the data, for example:</p>
<ul>
<li>
<p><a href="https://bcd-training.deno.dev/">BCD Training</a> (<a href="https://github.com/PaulKinlan/bcd-training">source</a>) - a web page with human readable versions of the data so it can be used in the LLM attached to <a href="https://paul.kinlan.me/ask-paul">Ask Paul</a></p>
</li>
<li>
<p><a href="https://time-to-stable.deno.dev/">Time to Stable</a> (<a href="https://github.com/PaulKinlan/time-to-stable">source</a>) - a web page that finds interesting stats about feature availability across browsers. Which browser is a sprinter and which is a plodder? or Which APIs are <a href="https://paul.kinlan.me/bcd-experimental-apis/">experimental</a>, etc</p>
</li>
<li>
<p><a href="https://baseline.deno.dev/">Baseline</a> (<a href="https://github.com/PaulKinlan/baseline">source</a>) - a web page that lists all the APIs that came to a the web in a given year. I use it to get a picture of what the <a href="https://web.dev/introducing-baseline/">Baseline project</a> might look like each year</p>
</li>
</ul>
<p>These projects can take a little while to create as I build a parser and display logic and then a website around it. I thought it might be neat to see if the Code Interpreter could take some of this burden off me.</p>
<p><strong>TL;DR</strong> - It can.</p>
<p>I'm very interested in Browser velocity, that is how quickly do browsers update, so I wanted to see if it could build some tools to analyse this data.</p>
<p>First up, I downloaded the <a href="https://unpkg.com/@mdn/browser-compat-data/data.json">JSON file</a> and uploaded it to the new <a href="https://chat.openai.com/?model=gpt-4-code-interpreter">Chat GPT model.</a></p>
<p>I know the file format so I had a bit of head start, so I asked it something simple. &quot;Find me the most recent versions of each browser.&quot;</p>
<p><img src="/images/Screenshot%202023-07-11%20at%2014.09.48.png" alt="Screenshot 2023-07-11 at 14.09.48.png"></p>
<p>It's wrong, but it's kind of incredible to watch it build the code that parses the file format and produces something that could work.</p>
<p>I suggested that it was wrong because Chrome is already a different version</p>
<p><img src="/images/Screenshot%202023-07-11%20at%2014.09.58.png" alt="Screenshot 2023-07-11 at 14.09.58.png"></p>
<p>It was incredible to see it adjust the algorithm to change how it parses the version number, however it was still wrong because it was showing future versions of browsers that haven't hit stable yet, so I simple suggested an improvement &quot;Close, but some of those have a 'release_date' after today. Can you get only the latest released versions of the browsers?&quot;</p>
<p><img src="/images/Screenshot%202023-07-11%20at%2014.10.07.png" alt="Screenshot 2023-07-11 at 14.10.07.png"></p>
<p>Damn!</p>
<p>All in all it's pretty great. Something that would have taken me about 30 minutes to make was created in about 2 minutes and a great thing is that it has the script so that you can run it locally too.</p>
<p>Yes, technically there are some errors for example if you check the code below you will see it tries to convert the browser version to an integer, yet some browsers have x.y.z versioning.... Yet, I'm still blown away.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> datetime <span style="color:#f92672">import</span> datetime
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Load JSON file</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#39;/mnt/data/data (1).json&#39;</span>, <span style="color:#e6db74">&#39;r&#39;</span>) <span style="color:#66d9ef">as</span> f:
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>load(f)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># The issue might be related to the version numbers being treated as strings rather than numbers.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># This can result in &#34;99&#34; being considered larger than &#34;110&#34; because &#34;9&#34; is larger than &#34;1&#34; in string comparison.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># To fix this, we need to convert the version numbers to integers where possible for the comparison.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># However, we need to handle cases where the version number is not a simple integer (e.g., &#34;9.3.0&#34;).</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">convert_version_to_int</span>(version):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Try to convert the version to an integer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> int(version)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ValueError</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># If that fails, convert the first part of the version to an integer</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> int(version<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;.&#39;</span>)[<span style="color:#ae81ff">0</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ValueError</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># If that still fails, return the version as is</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> version
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Function to convert a string to a datetime object</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">convert_string_to_date</span>(date_string):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> datetime<span style="color:#f92672">.</span>strptime(date_string, <span style="color:#e6db74">&#34;%Y-%m-</span><span style="color:#e6db74">%d</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get today&#39;s date</span>
</span></span><span style="display:flex;"><span>today <span style="color:#f92672">=</span> datetime<span style="color:#f92672">.</span>now()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Let&#39;s inspect the &#39;browsers&#39; key</span>
</span></span><span style="display:flex;"><span>browsers_data <span style="color:#f92672">=</span> data[<span style="color:#e6db74">&#39;browsers&#39;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Initialize an empty dictionary to store the most recent version of each browser</span>
</span></span><span style="display:flex;"><span>recent_versions <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Loop over each browser</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> browser, details <span style="color:#f92672">in</span> browsers_data<span style="color:#f92672">.</span>items():
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Get the &#39;releases&#39; field which contains version information</span>
</span></span><span style="display:flex;"><span>    releases <span style="color:#f92672">=</span> details<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;releases&#39;</span>, {})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Filter out versions where the release date is in the future</span>
</span></span><span style="display:flex;"><span>    releases <span style="color:#f92672">=</span> {version: details <span style="color:#66d9ef">for</span> version, details <span style="color:#f92672">in</span> releases<span style="color:#f92672">.</span>items()
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#39;release_date&#39;</span> <span style="color:#f92672">in</span> details <span style="color:#f92672">and</span> convert_string_to_date(details[<span style="color:#e6db74">&#39;release_date&#39;</span>]) <span style="color:#f92672">&lt;=</span> today}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Convert the versions to integers where possible</span>
</span></span><span style="display:flex;"><span>    releases <span style="color:#f92672">=</span> {convert_version_to_int(version): version <span style="color:#66d9ef">for</span> version <span style="color:#f92672">in</span> releases<span style="color:#f92672">.</span>keys()}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Get the most recent version</span>
</span></span><span style="display:flex;"><span>    recent_version <span style="color:#f92672">=</span> max(releases<span style="color:#f92672">.</span>keys(), default<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;No releases found&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Map the recent version back to its original string representation</span>
</span></span><span style="display:flex;"><span>    recent_version <span style="color:#f92672">=</span> releases[recent_version]
</span></span><span style="display:flex;"><span>    recent_versions[browser] <span style="color:#f92672">=</span> recent_version
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>recent_versions
</span></span></code></pre></div><p><img src="/images/Screenshot%202023-07-11%20at%2014.25.23.png" alt="Screenshot 2023-07-11 at 14.25.23.png"></p>
<p>Absolutely amazing! I think the summary is great too. The only nit right now is that the colours are hard to distinguish.</p>
<p><img src="/images/Screenshot%202023-07-11%20at%2011.23.03.png" alt="Screenshot 2023-07-11 at 11.23.03.png"></p>
<p>It kept all the context amazingly well, but as I was reading this, but the IE outlier make it hard to see what's happening today, so I asked it to &quot;Remove some of the outliers&quot;</p>
<p><img src="/images/Screenshot%202023-07-11%20at%2014.29.31.png" alt="Screenshot 2023-07-11 at 14.29.31.png"></p>
<p>Much clearer, but also WOW! Just by looking at the data there are stories that I can see :D</p>
<p>I'm not sure this post does my reaction any justice. I was completely blown away! Yes it's not an amazingly complex example, but I got to an output that I found incredibly useful in minutes and it's now left me thinking what else can I do with it.</p>
<p>If you are interested in the final code it generated check out this <a href="https://gist.github.com/PaulKinlan/7f25055ac899e1667a6f80119d7c3b05">gist</a>.</p>
<p>Let me know if you've played with the Code Interpreter, I'm certainly keen to learn more about how you are using it so that I can work out where I can take better advantage of it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>IndexedDB as a Vector Database</title>
      <link>https://paul.kinlan.me/idb-as-a-vector-database/</link>
      <pubDate>Sun, 07 May 2023 19:15:01 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/idb-as-a-vector-database/</guid>
      <description>I created a simple vector database called &amp;quot;Vector IDB&amp;quot; that runs directly in the browser using IndexedDB.  It&#39;s designed to store and query JSON documents with vector embeddings, similar to Pinecone, but implemented locally.  The API is basic with &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, and &lt;code&gt;query&lt;/code&gt; functions.  While it lacks optimizations like pre-filtering and advanced indexing found in dedicated vector databases, it provides a starting point for experimenting with vector search in the browser without relying on external services.  The project was a fun way to learn about vector databases and their use with embeddings from APIs like OpenAI.</description>
      <content:encoded> <![CDATA[<p>As I started to play with Open AI and some Generative ML ideas, I said <a href="https://paul.kinlan.me/building-ask-paul/">&quot;There are database companies that just focus on Vector search :mind-blown:&quot;</a>. My mind is still blown that this is an industry, but as I play with <a href="https://github.com/polymath-ai/polymath-ai">Polymath</a> and <a href="https://www.pinecone.io/">Pinecone</a> it is clear that they are useful services, and the tinkerer that I am wanted to tinker about with the idea of running this type of database directly in the browser. (If you are wondering &quot;What is a Vector Database and why do I need one?&quot;, then <a href="https://www.pinecone.io/learn/vector-database/">this article</a> is a good start)</p>
<p>The other week I spent some time building &quot;<strong>Vector IDB</strong>&quot; (<a href="https://github.com/PaulKinlan/idb-vector">source</a>) as an experiment for making something similar to the structure that Pinecone has. The API surface is relatively plain, there are all the standard utilities: <code>insert</code>, <code>delete</code>, <code>update</code>, <code>query</code> and they can be used as follows.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">VectorDB</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;idb-vector&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">db</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">VectorDB</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">vectorPath</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;embedding&#34;</span>
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key1</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">insert</span>({ <span style="color:#a6e22e">embedding</span><span style="color:#f92672">:</span> [<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>], <span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;ASDASINDASDASZd&#34;</span> });
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key2</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">insert</span>({ <span style="color:#a6e22e">embedding</span><span style="color:#f92672">:</span> [<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">4</span>], <span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;GTFSDGRG&#34;</span> });
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key3</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">insert</span>({ <span style="color:#a6e22e">embedding</span><span style="color:#f92672">:</span> [<span style="color:#ae81ff">73</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">213</span>, <span style="color:#ae81ff">3</span>], <span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;hYTRTERFR&#34;</span> });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">update</span>(<span style="color:#a6e22e">key2</span>, { <span style="color:#a6e22e">embedding</span><span style="color:#f92672">:</span> [<span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">4</span>], <span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;UPDATED&#34;</span> });
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#66d9ef">delete</span>(<span style="color:#a6e22e">key3</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Query returns a list ordered by the entries closest to the vector (cosine similarity)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">query</span>([<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">2</span>, <span style="color:#ae81ff">3</span>], { <span style="color:#a6e22e">limit</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">20</span> }));
</span></span></code></pre></div><p>Because it is just a wrapper over IndexedDB you can throw JSON documents at it, and as long as it has an instance of an <code>Array</code> on the property referenced by <code>vectorPath</code> all should just work. It will create a IndexedDB for you with an objectStore and an index that is based on the defined vector too.</p>
<p>Now, this is no way a complete solution. There are no optimisations of the index; it doesn't do any pre-filtering to optimise they size query space; it doesn't do post-filtering of results (outside of the [limit] argument) etc etc. The goal was to be a simple wrapper to get you started quickly, if you already have a relatively complex IndexedDB integration for your site you will see by checking out some of the code that this is something that you can do you without too much hassle.</p>
<p>I enjoyed building this creating this project because it I got to learn a bit about Vector Databases and how they can be used when storing and querying <code>embeddings</code> from APIs like Open AI directly inside a browser without having to use a hosted solution.</p>
<p>If you have experience building these types of databases, I would love to hear from you and learn what I might be missing.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Bookmarklet: Eyedropper</title>
      <link>https://paul.kinlan.me/eyedropper-bookmarklet/</link>
      <pubDate>Tue, 02 May 2023 13:23:56 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/eyedropper-bookmarklet/</guid>
      <description>This blog post introduces a bookmarklet utilizing the EyeDropper API for quickly grabbing color information in Chromium-based desktop browsers.  The bookmarklet simplifies color selection by opening the eyedropper tool and returning the chosen color&#39;s sRGBHex value in an alert box.  A link to a related blog post about creating a similar Chrome extension is also included.</description>
      <content:encoded> <![CDATA[<p>I was reading Stefan Judis's awesome &quot;<a href="https://webweekly.email/">Web Weekly</a>&quot; and in <a href="https://www.stefanjudis.com/blog/web-weekly-100/">this weeks post</a> he mentioned the <code>EyeDropper</code> API in <a href="https://developer.mozilla.org/en-US/docs/Web/API/EyeDropper/open#browser_compatibility">Chromium Desktop browsers only</a> - I totally missed this and because I frequently have to grab color information for slide design I need something quick to hand. Bookmarklets are quick to hand, so I built one for you.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JavaScript" data-lang="JavaScript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">e</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">EyeDropper</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">open</span>().<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">d</span> =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">alert</span>(<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">sRGBHex</span>);
</span></span><span style="display:flex;"><span>}).<span style="color:#66d9ef">catch</span>(<span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>)
</span></span></code></pre></div><p>EyeDropper &lt;— Just drag this to your bookmark bar.</p>
<p>And if you are interested in a Chrome Extension, check <a href="https://patrickbrosset.com/articles/2021-11-24-how-i-built-an-eye-dropper-browser-extension/">out this post</a> by <a href="https://patrickbrosset.com/resume/">Patrick Brosset</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Querying browser compat data with a LLM</title>
      <link>https://paul.kinlan.me/querying-browser-compat-data-with-a-llm/</link>
      <pubDate>Tue, 11 Apr 2023 13:19:43 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/querying-browser-compat-data-with-a-llm/</guid>
      <description>I explored using LLMs for checking web API browser compatibility.  Existing LLMs struggle with outdated data, so I experimented with MDN&#39;s Browser Compat Data (BCD).  Initial trials using raw BCD JSON with GPT-4 had limitations. To improve this, I converted the BCD into English descriptions of API support and loaded it into a Polymath instance.  This allows natural language queries about API compatibility across browsers, like &amp;quot;Is CSS Grid supported in Safari, Firefox, and Chrome?&amp;quot; or &amp;quot;When was CSS acos available in Chrome?&amp;quot;.  The results are promising, but further refinement is needed to ensure accuracy and reliability.</description>
      <content:encoded> <![CDATA[<p>I've been noodling about a lot with LLMs recently and naturally I wanted to see if they could help me with my role. There's a lot of places I've found them to be useful, but an area where I've struggled is their data sources not being up to date with the current state of the web.</p>
<p>I build a lot of sites and speak to a lot of Web Developers and a huge problem we have is knowing what APIs are supported so that I can use and rely on it in my sites without having to worry about compatibility as much. There are a number of sites that have this information (<a href="https://caniuse.com">canisuse</a> and <a href="https://developer.mozilla.org/en-US/">MDN</a>), however if the LLM isn't up to date on the latest state of browsers then we can't query compatibility information.</p>
<p>Luckily, the raw <a href="https://github.com/mdn/browser-compat-data">Browser Compat Data</a> by MDN is here to the rescue (<a href="https://paul.kinlan.me/what-is-new-on-the-web/">again</a>), and the team build and maintain the BCD data as an ongoing and always up to date resource in JSON. This gave me an idea - if I can load this data into my <a href="https://github.com/polymath-ai/polymath-ai">Polymath</a> instance then I might be able to ask a question like:</p>
<ul>
<li>I need to use Safari, Firefox and Chrome - is CSS Grid ok to use?</li>
<li>When did CSS Grid become useable in Safari, Firefox and Chrome?</li>
</ul>
<p>The hypothesis I had was that we have all the data so an LLM might be able to infer decent results to questions like the above.</p>
<p>The first step I tried was to pass some raw JSON data into GPT-4 and see what it could infer - It worked okay but it doesn't have a clear comprehension of the data format so it made a lot of mistakes, for example if it saw support value &quot;mirror&quot;, it outputted &quot;supported in Chrome&quot; and not the base engine (i.e, Safari on iOS mirrors Safari or WebKit). The answer was good enough for me that I decided that I wanted to flesh it out a bit more and try and massage the input data into something the LLM might better work with.</p>
<p>My hypothesis was that the LLM might parse written English better, so I quickly threw together a complete list of all the APIs tracked by <a href="https://bcd-training.deno.dev/">BCD with an English description of the support matrix</a> that expanded on the data in the JSON file. For example, here is the support data for <a href="https://bcd-training.deno.dev/feature?id=css.types.acos">acos</a> (you can see the code in my <a href="https://github.com/PaulKinlan/bcd-training">github repo</a>).</p>
<p>I then ingested all the data into my Polymath instance (<a href="https://paul.kinlan.me/building-ask-paul/">creating the Embedding vectors</a>) and uploaded it to the data store so that you can now query it like so: <a href="https://paul.kinlan.me/ask-paul?query=When+did+CSS+acos+arrive+in+Chrome%3F">When did CSS acos arrive in Chrome?</a> and <a href="https://paul.kinlan.me/ask-paul?query=I+need+to+use+Safari%2C+Firefox+and+Chrome+-+can+I+use+css.acos%3F">I need to use Safari, Firefox and Chrome - can I use css.acos?</a></p>
<p>A fun experiment and it seems to work well, but it's only as good as the data I put in and the quality of the LLM (I imagine it can make up things so I wouldn't really trust the output just yet).</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
