<?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>Sat, 27 Dec 2025 12:00:00 +0000</lastBuildDate>
    
	<atom:link href="https://paul.kinlan.me/index.xml" rel="self" type="application/rss+xml" />
    
    
    
    <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>
<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-fallback" data-lang="fallback"><span style="display:flex;"><span>const isOdd = await f`is a given number odd?`;
</span></span><span style="display:flex;"><span>console.log(isOdd(101))
</span></span></code></pre></div><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>
<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-fallback" data-lang="fallback"><span style="display:flex;"><span>const spaceData = await f`fetch JSON from https://api.spaceflightnewsapi.net/v4/articles/`:
</span></span><span style="display:flex;"><span>const news = await spaceData();
</span></span></code></pre></div><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>
<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-fallback" data-lang="fallback"><span style="display:flex;"><span>// Describe the data structure so the the UI prompt has a better idea of what to build.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>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.`;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>// Describe the data
</span></span><span style="display:flex;"><span>const schemeDescription = schema(spaceData);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>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;.
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>&lt;output&gt;${JSON.stringify(schemeDescription)}&lt;/output&gt;`;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>document.body.appendChild(spaceUI(spaceData));
</span></span></code></pre></div><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>
<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-fallback" data-lang="fallback"><span style="display:flex;"><span>import { create } from &#34;@paulkinlan/f&#34;;
</span></span><span style="display:flex;"><span>import {
</span></span><span style="display:flex;"><span>    prompt,
</span></span><span style="display:flex;"><span>    ClaudePromptConfiguration
</span></span><span style="display:flex;"><span>} from &#34;@paulkinlan/reactive-prompt/claude&#34;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>const config = new ClaudePromptConfiguration({ debug: true });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>config.dangerouslyAllowBrowser = true;
</span></span><span style="display:flex;"><span>config.key = window.prompt(&#34;API Key&#34;);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>// Because we can use many different LLMs we need to pick one.
</span></span><span style="display:flex;"><span>const f = create(
</span></span><span style="display:flex;"><span>  prompt,
</span></span><span style="display:flex;"><span>  config
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>const fetchNews = await f`fetch JSON from https://api.spaceflightnewsapi.net/v4/articles/`;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>// Get the news.
</span></span><span style="display:flex;"><span>const news = await fetchNews();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>// If we can describe the object UI gnereation is a bit easier.
</span></span><span style="display:flex;"><span>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.`;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>const schemeDescription = schema(news);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>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;.
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>&lt;output&gt;${JSON.stringify(schemeDescription)}&lt;/output&gt;`;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>document.body.appendChild(spaceUI(news));
</span></span></code></pre></div><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>
<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-fallback" data-lang="fallback"><span style="display:flex;"><span>import { board } from &#34;@google-labs/breadboard&#34;;
</span></span><span style="display:flex;"><span>import { sentiment } from &#34;@paulkinlan/transformerjs-breadboard-kit&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>export default await board(({ text, model }) =&gt; {
</span></span><span style="display:flex;"><span>    text.isString();
</span></span><span style="display:flex;"><span>    model.isString();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    const summarynode = transformersjs.summarize({
</span></span><span style="display:flex;"><span>      $id: &#34;summary&#34;,
</span></span><span style="display:flex;"><span>      input: text,
</span></span><span style="display:flex;"><span>      model,
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    return summarynode.to(base.output());
</span></span><span style="display:flex;"><span>  }).serialize({
</span></span><span style="display:flex;"><span>  title: &#34;Summary board&#34;,
</span></span><span style="display:flex;"><span>  description: &#34;Runs the summarization analysis with transformer JS&#34;,
</span></span><span style="display:flex;"><span>  version: &#34;0.0.2&#34;,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div>]]></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>
    
    <item>
      <title>Building Ask Paul</title>
      <link>https://paul.kinlan.me/building-ask-paul/</link>
      <pubDate>Fri, 07 Apr 2023 17:36:14 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/building-ask-paul/</guid>
      <description>I built Ask Paul, a generative AI demo that answers front-end web dev questions using my content. It leverages Polymath-AI to index content, find related concepts, and generate summaries by creating embedding vectors, using cosine-similarity, and querying OpenAI.  The implementation has a UI, a Polymath Client, and a Polymath Host.  It&#39;s super cool how accessible this tech is now!</description>
      <content:encoded> <![CDATA[<p>I've been doing a lot of experimentation with Generative Machine Learning and one of the demo's that I've build is called &quot;<a href="https://paul.kinlan.me/ask-paul">Ask Paul</a>&quot;. You can ask me nearly any front-end web development question and the software will give you a direct answer if it can and links to further reading across the sites that I create content for (this blog, <a href="http://web.dev">web.dev</a> and <a href="http://developer.chrome.com">developer.chrome.com</a>)</p>
<p>You can try it with a couple more queries:</p>
<ul>
<li><a href="https://paul.kinlan.me/ask-paul?query=how+do+you+centre+a+div%3F">How do you centre a div?</a></li>
<li><a href="https://paul.kinlan.me/ask-paul?query=Can+you+use+CSS+Grid+with+Safari+on+iOS+%28and+since+when%29%3F">Can you use CSS Grid with Safari and since when?</a></li>
<li><a href="https://paul.kinlan.me/ask-paul?query=What+is+the+code+to+connect+to+a+device+with+Web+USB%3F">What is the code to connect to device with Web USB?</a></li>
</ul>
<p>I'm very happy with the results, and I thought it would be nice to document how I built it because I believe that the technology is a lot more accessible than it was just 6 months ago and it can be integrated into any site.</p>
<p>Goals:</p>
<ul>
<li>I wanted a search function that could easily index content that I gave it</li>
<li>I wanted the search to be able to understand related concepts e.g, &quot;PWA&quot; and &quot;Progressive Web App&quot; are the same thing.</li>
<li>I wanted to see if I could get the machine to generate a summary that would answer the person's question</li>
<li>Did not require JavaScript, and rendered a UI instantly</li>
</ul>
<p>I used the <a href="https://github.com/polymath-ai/polymath-ai">Polymath-AI</a> project as the main infrastructure for this project. Polymath-AI is pretty great (I've now contributed some fixes) and it's interesting to me not because of how I integrated it to my site, but rather it's a protocol to query and interrogate a web of repositories of knowledge. If you check out the <a href="https://www.npmjs.com/package/@polymath-ai/client">CLI</a> you can see that you can query any polymath instance ( e.g, <code>npx polymath ask &quot;What happened to Web Intents?&quot; --servers https://paul.kinlan.me/polymath --openai-api-key [YOUR OPEN AI API KEY]</code> ). I thought that this was incredibly powerful because I can ACL my own data and give <em>you</em> complete access to my public data, and selected people access to private data (I'm a big <code>logseq</code> user and I have a private repo that I would like to be able to interrogate).</p>
<p>It's worth giving a quick overview of Polymath because it does three things:</p>
<ol>
<li>It ingests data from the owner of the instance (me in this case), and for each piece of content creates an <code>embedding vector</code> which is then stored in a <code>pinecone</code> database.</li>
<li>It finds content and links that are related to a persons query. First by creating an <code>embedding vector</code> for the persons query and then use cosine-similarity to compare the query vector against all the known contents embedding vector.</li>
<li>It discovers the most similar piece of content to the person's query to insert directly into a query to the Open AI API, with a question of the form: <code>Answer the question as truthfully as possible using the provided context, and if the answer is not contained within the text below, say &quot;I don\'t know&quot;.\n\nContext:{context}\n\nQuestion: {query}\n\nAnswer:</code>. <code>context</code> is the most similar document, and <code>query</code> is the person's query.</li>
</ol>
<p>And it works incredibly well. My mind was completely blown the first time that I saw it working.</p>
<p>I won't cover the ingestion process in this post as it's basically <code>npx polymath ingest rss [url]</code> .</p>
<p>It turns out that the plumbing to get this working is achievable and can be hooked up relatively quickly. If you want to host your own instance, you can do something similar to what I've done with the three components that I created.</p>
<ol>
<li><strong>A UI</strong> - Renders an HTML streaming response from the Polymath Client - that is all. [<a href="https://github.com/PaulKinlan/paul.kinlan.me/blob/main/api/ask-paul.ts">direct link</a>] ( about 30 lines of HTML and a <code>fetch</code> request)</li>
<li><strong>A Polymath Client</strong> - takes a person's query and interacts with any Polymath host - it's configured to connect to my Polymath Host and once it has the data, then queries Open AI with the above request. [<a href="https://github.com/PaulKinlan/paul.kinlan.me/blob/main/api/polymath.js">direct link</a>]</li>
<li><strong>The Polymath Host</strong> - the implementation of the protocol defined by Polymath - this directly queries my configured pinecone database and will return embeddings that are closest to your query [<a href="https://github.com/PaulKinlan/paul.kinlan.me/blob/main/api/polymath/ask.ts">direct link</a>] - They great thing is that you don't need to use my UI to query it, you can use your own client (or CLI) <code>npx polymath ask &quot;Why is Paul so handsome?&quot; --servers https://paul.kinlan.me/polymath --openai-api-key [YOUR OPEN AI API KEY]</code></li>
</ol>
<p><em>Just a note if you are implementing this</em>: I had to split it into three pieces because the Polymath client currently only supports the Node runtime and my UI needed to be able to stream a response from Vercel (which at the time was only available on the &quot;Edge Server&quot;). If I can get Node streaming working, then the UI and Client code would be be merged.</p>
<p>And that's it. I'm just at the start of an ML assisted journey and I still struggle to know what I can do with LLM but it's been a wild couple of weeks trying to learn how it all workings.</p>
<p>It's also wild to me that I can quickly build Generative ML experiences that work directly into my site easily. That I can implement your own site search++ quickly and it's a solved problem. There are database companies that just focus on Vector search :mind-blown:</p>
<p>Interesting links:</p>
<ul>
<li><a href="https://www.npmjs.com/package/@polymath-ai/client">Client</a></li>
<li><a href="https://www.npmjs.com/package/@polymath-ai/cli">CLI</a></li>
<li><a href="https://www.npmjs.com/package/@polymath-ai/host">Host</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Talk: &#34;Aiming for the future&#34; at Bangor University</title>
      <link>https://paul.kinlan.me/aiming-for-the-future-at-bangor-university/</link>
      <pubDate>Tue, 21 Mar 2023 12:51:19 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/aiming-for-the-future-at-bangor-university/</guid>
      <description>I presented &amp;quot;Aiming for the Future&amp;quot; at Bangor University, exploring computing&#39;s evolution from the Difference Engine to the modern era, focusing on content/data delivery shifts. I proposed that Machine Learning, especially Generative AI, is the next major computing wave, akin to the Web&#39;s rise in the early 2000s, potentially mechanizing mental labor.  The Student Expo showcased many final-year projects incorporating AI, from creative tools to practical problem-solving, indicating the growing importance of AI in various fields.</description>
      <content:encoded> <![CDATA[<p>I was honoured to be able to present at the &quot;<a href="https://www.bangor.ac.uk/scsee">School of Computer Science and Electronic Engineering</a>&quot; last week with a talk called &quot;Aiming for the Future&quot; [<a href="https://paul.kinlan.me/images/Bangor%20Keynote_%20Aiming%20for%20the%20future.pdf">pdf</a>].</p>
<p>I had a lot of fun creating this talk where I could go from the earliest computing with the Difference Engine all the way to today and try and talk about the evolutions of computing and possibility at every transition (I tied the transitions to delivery of content/data).</p>
<p>I wanted to try and hit a note that when I was coming out of University in the early 2000's we were able ride on the ascendancy of the Web and students coming out of university today are at the foot of potentially a couple of new waves that today's students can take advantage of.</p>
<p>I placed my bet on ML being the next wave, specifically the Generative aspect as a step change in the way people use computers. I could be totally wrong, but it's an area that I am personally investing a lot of time re-learning (heh - it's almost 20 years since my AI based dissertation) and to me it feels that we are at the start of a significant shift where I am in a lucky position that the web is the best medium to deliver this change.</p>
<p>Interestingly, earlier in the day I had a discussion with Carwyn Edwards and Joseph Mearman made an interesting point about a new potential Industrial Revolution where instead of Mechanising physical labour, we're at a point where mental labour is now Mechanised. I thought it was a such an evocative statement - in the same spirit as a &quot;Bicycle for the mind&quot; 40 years ago. I asked Chat GPT4 something similar the day before and it said:</p>
<p><img src="/images/Screenshot%202023-03-14%20at%2021.07.13.png" alt="A description of what Chat GPT thinks ML is if a computer is a bicycle for the mind. A self-driving car."></p>
<p>Heh. Thinking a lot of itself...</p>
<p>I wrote this talk before I had seen what was happening at the University and as the speaker at the event I also got invited to the Student Expo earlier in the day. The Expo was great, it was full of the Final year students projects and it was great to see what the students are up to and they are solving interesting challenges. It was also very interesting to see how many students are applying many different aspects of AI to practical problems (I'd say 1/3rd to a 1/2 of the projects). On one hand there were high-level Music and Lyric generators with Chat GPT; practical developers tools like a Unity plugin with Stable Diffusion; to the very low-level Fibre Optic fault detection with Convolutional Neural Networks. It was great to see the entire spectrum and it was interesting to see how many of the students were focusing on this new area.</p>
<p>The students might have just picked up on the zeitgeist and worked on that, but I think there is something far more fundamental happening given the breadth and the depth of learning. Either way, it was great to see and I left excited for the future.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>BCD - Experimental APIs bcd</title>
      <link>https://paul.kinlan.me/bcd-experimental-apis/</link>
      <pubDate>Mon, 13 Feb 2023 09:45:35 +0000</pubDate>
      <author>paul.kinlan@gmail.com (Paul Kinlan)</author>
      <guid>https://paul.kinlan.me/bcd-experimental-apis/</guid>
      <description>I&#39;ve added a new feature to time-to-stable that lists experimental APIs across browsers using BCD. This helps developers track experimental APIs, understand their stability, and consider the implications for website integration.  It helps answer questions about cross-browser compatibility and potential deprecation, informing decisions about using these APIs.</description>
      <content:encoded> <![CDATA[<p>This is just a small update. I've spent a bit of time adding some features to &quot;<a href="https://time-to-stable.deno.dev/">time-to-stable</a>&quot;. As I try to think about what is <a href="https://paul.kinlan.me/what-is-new-on-the-web/">stable across the web platform</a> it is useful to think about what APIs are marked as experimental.</p>
<p>I added a new page to the site which lets you discover the APIs across a selected list of browsers that are still <a href="https://paul.kinlan.me/what-is-new-on-the-web/">marked as experimental in BCD (Browser Compat Data)</a>.</p>
<p><img src="/images/Screenshot%202023-02-13%20at%2009.59.34.png" alt="A list of APIs that are marked as experiemntal in Chrome, Safari or Firefox"></p>
<p>Why is this important? For me at least it is useful to know when an API is not something that I can rely on across because it's not in all browsers, and at the same time an API might be considered experimental for an incredibly long time (6 years in the screenshot above) - that gives you an idea about the potential importance of other browser vendors to standardise on this.</p>
<p>It is fair to ask the question: If it's just in browser X and it has for a long time, is it something that will get deprecated?</p>
<p>Now, there might not be an answer to that, but at least you have more data to help you make a decision on if it is something you want to integrate into your site and have productive conversations with your stakeholders and team.</p>
<p>Let me know what I can do to improve this.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
