<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Paul Kinlan on Medium]]></title>
        <description><![CDATA[Stories by Paul Kinlan on Medium]]></description>
        <link>https://medium.com/@paul_kinlan?source=rss-a7bf6b8f7cd0------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*ucd4qk8xB0em6OS7.png</url>
            <title>Stories by Paul Kinlan on Medium</title>
            <link>https://medium.com/@paul_kinlan?source=rss-a7bf6b8f7cd0------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 02:46:31 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@paul_kinlan/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[the prompt is the program]]></title>
            <link>https://medium.com/@paul_kinlan/the-prompt-is-the-program-c385a8d467a8?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/c385a8d467a8</guid>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Sat, 21 Feb 2026 19:15:11 GMT</pubDate>
            <atom:updated>2026-06-13T15:56:00.824Z</atom:updated>
            <content:encoded><![CDATA[<p>I built an application recently. It has a contact directory, a database, cross-referencing, topic tracking, meeting notes, project management, backup and restore, automated sync, conflict resolution, and a task queue. The entire program is a markdown file.</p><p>It’s just a file. It sits in a git repo with some bash scripts for plumbing, but the actual behaviour (what the system does, how it organises data, when it evolves) lives in natural language instructions. The prompt is the program.</p><p>This started from a gap in my own workflow. I used <a href="https://logseq.com/">Logseq</a> every day for a long time and it worked well: markdown, daily logs, cross-references. Then I went on holiday for a couple of weeks, stopped, and never got back into it. The problem was never the app. It was the operating model. I needed memory I controlled, that worked within enterprise constraints, and that didn’t lock context inside a platform.</p><p>As I spent more time with large language models, I kept coming back to a few things.</p><p>First, memory. Why is it treated as a feature when it should be the base state? And where should it live if I’m serious about keeping it mine? Most chat systems still feel built for isolated conversations. If one thread remembers the previous one, that can be a bonus, but it can also be incredibly creepy because it’s not always expected. But some systems, you expect memory to be the baseline, and that’s what I wanted to explore, within the constraints of it being local, private, and mine.</p><p>But there was something else. I was using LLMs to build applications, libraries, tools, services (I even <a href="https://aifoc.us/super-apps">replaced most of my apps with an LLM on a trip to Japan</a>), and I noticed that sometimes a model would create a small, complete program from a single instruction to solve a very specific task. No scaffolding, no framework, just an instruction and a working result. That got me wondering: how far could you push this? Could you build systems that are emergent, where the application grows and restructures itself as the conversation continues?</p><p>So I wrote a behaviour contract in markdown and pointed an LLM at it.</p><p>The common objection is fair: prompts are too loose to count as programs. But I’ve started to find that high-level instructions are enough, and often better. Here’s what my actually specifies:</p><ul><li><strong>Data model:</strong> daily entries in entries/, people and topics indexed separately, TODOs as first-class objects, ideas and projects explicit, meetings with dedicated structure. All plain-text markdown.</li><li><strong>Behaviour on input:</strong> when the user journals something, create the daily entry, create or update any referenced topic/person/idea/TODO files, cross-link everything, update the living index. Every action produces structured state.</li><li><strong>Boundaries:</strong> personal data stays in specific folders, excluded from the public repo by policy. Private data is marked as such. The system knows what not to share.</li><li><strong>Evolution:</strong> the system is expected to change. The prompt tells the agent to propose new folder types when it notices recurring patterns, update its own conventions, and log structural changes.</li></ul><p>The last point kinda blew my mind. The prompt tells the agent to modify itself.</p><p>Within a week of use, the system proposed a meetings/ folder because I kept logging meetings inline. It proposed a projects/ folder to separate active codebases from broader topics. And when I noticed I couldn&#39;t easily see which TODOs were still active, I asked the model how we might solve it. It proposed a todos/done/ subfolder to archive completed items without losing history. Each time, it updated its own instructions to include the new convention.</p><p>The program can rewrite its own source code. Early on it happened a lot as the system found its shape. Now it happens less and less, which I think is actually a good sign. The structure has settled into something that works for me and my workflow.</p><p>That is weird, and a little bit scary, and also genuinely useful.</p><p>A concrete example: we were renovating a house and picking paint colours online. I told the journal to remember the URL, extract the colour codes, and remind me to get samples the next day. I asked a journal to do work for me on data I’d just given it. That in itself is kind of crazy.</p><p>The next day, the reminder was there. I went to the store. The store wanted <a href="https://en.wikipedia.org/wiki/RAL_colour_standard">RAL codes</a>, a standard colour format used across the paint industry, and couldn’t use what I had from the website. I had no clue what a RAL code was and no clue how to convert it. There were <a href="https://rgb.to/ral">tools</a> <a href="https://hextoral.com/">online</a> that could do it, but I didn’t have time. So I just asked the journal to continue the task: find the equivalent local format and convert the values.</p><p>By the time I got home, the conversion was done:</p><pre>| Original Colour | Brand Code | Valspar Equivalent | Valspar Code | <br>| --------------- | ---------- | ------------------ | ------------ | <br>| Manchester Tan | BM HC-81 | Rattan Basket | 3007-10C | <br>| Bleeker Beige | BM HC-80 | Ancient Relic | M302 | <br>| Shaker Beige | BM HC-45 | Garden Rain | V094-2 | <br>| Grant Beige | BM HC-83 | Vanderbilt Beach | V134-1 | <br>| Canvas Tan | SW 7531 | Vanderbilt Beach | V134-1 | <br>.... Shortened .... <br>| Kilim Beige | SW 6106 | *(give SW code to mixer)* | N/A | <br>| Wool Skein | SW 6148 | *(give SW code to mixer)* | N/A | <br><br>BM is Benjamin Moore, SW is Sherwin-Williams. Nearby RAL matches (not exact): RAL 1013, 1014, 1015, 7032 [ shortened ......]</pre><p>I went back to the store the following day. I learned a lot more about what RAL codes actually are and how they don’t cleanly map to the colours we’d picked from the website. But the ones the system suggested were, when I checked them physically, close enough to what we needed as inspiration.</p><p>The journal wasn’t storing context. It was solving the next step in a chain of work, <a href="https://aifoc.us/ai-powered-site-mashups">mashing up data across sources</a> like an agent would. The prompt defined a system that could pick up a task, carry it across sessions, and act on it. Not because I wrote task-management code, but because the instructions said to.</p><p>The latest evolution: the system now suggests work it can do on its own. I have a backlog of article ideas, research tasks, and project seeds captured in the journal. The prompt instructs the agent to review these at the start of each session and suggest things it could research in the background. Gather data, find prior art, identify logical gaps, surface follow-on questions.</p><p>I didn’t write a task scheduler. I wrote a sentence that said “review ideas and suggest things you could work on.” And it does.</p><p>The same set of instructions works reasonably well across Claude Code, Codex, and Gemini CLI. It’s not perfect, but I can use them more or less interchangeably. That’s surprising. These LLM CLIs are a new kind of runtime, and the fact that a markdown file can act as a program across all of them opens up a way of building software that I hadn’t really thought about before.</p><p>I’m not rejecting existing tools. Logseq is excellent. My friend <a href="https://robdodson.me/">Rob Dodson</a> is doing great work at the intersection of second brain thinking and large language models. His piece on <a href="https://robdodson.me/posts/i-gave-my-second-brain-a-gardener/">giving his second brain a gardener</a>, his writing on <a href="https://robdodson.me/posts/how-i-built-my-mobile-second-brain/">building a mobile second brain</a>, and his thoughts on <a href="https://robdodson.me/posts/your-ai-chatbot-is-a-gatekeeper/">why AI chatbots become gatekeepers</a> are all worth reading, and at the same time, I now have a tool that works well enough for me.</p><p>And I’ll be honest: there’s a good chance I fall away from this model eventually. It would be a stretch to pretend this has permanently replaced everything.</p><p>But the thing that keeps it alive is not a feature. It’s the self-organising behaviour. I can type or dictate, and the system turns that into state: reminders, links, TODOs, references, context for next week. The structure emerged from use, not from upfront design.</p><p>Most people still design from the UI outward. I started with a minimal behaviour contract and let the structure emerge from what I was actually doing day to day with the system.</p><p>What surprised me is how little code I had to write. The bash scripts handle plumbing: backup, restore, merge, sync. And honestly, if I’d encoded that in the prompt too, the model could probably manage it. I just didn’t have the confidence to do that at the start. The actual program, the part that decides what to do with my input and how I organise my life, is prose.</p><p>I’d really encourage people to explore this. In the last six months or so, something has shifted. Models have become dramatically better at tool calling. The concept of <a href="https://sketch.dev/blog/agent-loop">agentic loops</a> has developed far enough that you can keep these systems on rails, at least for simple enough use cases. We’ve hit a point we’ve never been at before: you can build a real, useful system by having a conversation with an LLM and writing down the instructions for how it should behave. No framework, no app store, no deployment pipeline. Just prose, an LLM as a runtime, and a loop. I can imagine more and more people starting to think about building programs, automations, and personal systems this way.</p><p><em>Originally published at </em><a href="https://aifoc.us/prompt-is-the-program/"><em>https://aifoc.us</em></a><em> on February 21, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c385a8d467a8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[dead framework theory]]></title>
            <link>https://medium.com/@paul_kinlan/dead-framework-theory-97c2904012f4?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/97c2904012f4</guid>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Sun, 12 Oct 2025 23:00:50 GMT</pubDate>
            <atom:updated>2026-06-12T16:11:00.667Z</atom:updated>
            <content:encoded><![CDATA[<p><em>These are my opinions and are ruminations on what might be happening as more and more developers use LLMs and Frameworks to build on the web.</em></p><p>In October last year I wrote “ <a href="https://paul.kinlan.me/will-we-care-about-frameworks-in-the-future/">will developers care about frameworks in the future?</a> “ predicting that LLMs would abstract away framework choice. I was wrong-or at least, wrong about the timeline.</p><p>The reality is more interesting and more permanent: <strong>React isn’t competing with other frameworks anymore. React has become the platform.</strong> And if you’re building a new framework, library or browser feature today, you need to understand that you’re not just competing with React-you’re competing against a self-reinforcing feedback loop between LLM training data, system prompts, and developer output that makes displacing React functionally impossible.</p><p>If you look at what Replit, Bolt, and similar tools are doing, they’re not trying to abstract away frameworks-they’re <a href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/7e9f6102c7d164dfdbfca3bfd66f3d8ad5c0b2cc/Open%20Source%20prompts/Bolt/Prompt.txt#L275">explicitly hardcoding React into their system prompts</a>. They have to. If you’re building a tool today to attract developers, you need to give them code they can maintain. And “code developers can maintain” now means “React” for the vast majority of web developers.</p><p><a href="https://trends.builtwith.com/javascript/React">According to builtwith.com, there were +13m sites outside of the top 1m deployed with React in the last 12 months</a>. Look at these curves:</p><p>However, looking at <a href="https://httparchive.org/reports/techreport/tech?tech=ALL#adoption">HTTP Archive</a>, it tells a different story. React usage has stalled at 1.2 million mobile origins on mobile vs 55 million origins as reported by Builtwith.</p><p>The dataset sizes are vastly different. HTTP Archive looks over <a href="https://aifoc.us/images/http-archive-origins-over-time.png">some 12–16 million origins</a>, while <a href="https://trends.builtwith.com/javascript/React">Builtwith is reportedly looking</a> at some <a href="https://aifoc.us/images/built-with-coverage.png">414 million <em>root domains</em></a>. Sites also don’t get into HTTP Archive unless there is some amount of usage and many sites in Builtwith might be parked domains or sites that are not actively being used.</p><p>Looking at the top 1m, the detection rate is more aligned: 140k vs 160k.</p><p>We’re looking at about 12–18% of sites in the top 1m. Take all these numbers with a pinch of salt. The detection can be broken, the datasets are different sizes and the definitions of what is being measured are different. But the trends feel undeniable: React’s growth continues while competitors <a href="https://aifoc.us/images/builtwith-angular-12mo.png">like Angular, sadly stagnate</a>.</p><p>So what has driven the uptick in React sites? My read of the data suggests LLM tools over the last 12–18 months are preferring to output React code.</p><p>Look at token growth on OpenRouter. Programming tools are <a href="https://aifoc.us/token-slinging">burning through billions of tokens a day</a> via just one gateway. The curves look similar:</p><p>Correlation is not causation, and only the tool creators see the full picture as tokens flow through their systems. But the timing is striking: massive token growth coinciding with massive React deployment growth.</p><p>The models and the tools are preferring the tools that developers are already using, and it’s driving a self-reinforcing cycle of adoption. If you are launching a new API or tool today, you need to consider how it will be adopted by the ecosystem and how to get it into the training corpus of the LLMs.</p><p>We have two loops of feedback in play here:</p><ol><li>React dominates the existing web (~13M+ new sites in 12 months)</li><li>LLMs train on the existing web</li><li>LLMs output React by default</li><li>New sites built with LLMs use React</li><li>More React sites exist for future training</li><li>Go to step 2</li></ol><p>And&amp;mldr;</p><ol><li>React dominates the developer ecosystem</li><li>IDEs and tools that developers preferntially output React</li><li>Tools ask LLMs to output React by default</li><li>New sites built are using React</li><li>More React sites exist to increase demand for tools to output React</li><li>Go to step 1</li></ol><p>I don’t actually know if this is bad or good. We’re getting more sites on the web and they’re all pretty high quality. But it does create barriers for new frameworks, tools and web platform features that we need to understand. Specifically when:</p><ol><li>Your framework isn’t in the training data because it’s new</li><li>Tool creators hardcode React because that’s what developers know today</li><li>Developers expect React output because that’s what works</li><li>Companies won’t use your framework if their developers can’t maintain it</li><li>React has thousands of libraries; you have dozens</li></ol><p>If you launch a new framework, library or browser feature today, even if it’s technically superior, you need to:</p><ul><li>Get into LLM training data (12–18 month lag minimum)</li><li>Convince tool creators to modify system prompts (requires existing adoption)</li><li>Build a comprehensive library ecosystem (years of development)</li><li>Overcome developer inertia and get developers to ask for it</li></ul><p>By the time you’ve done step 1, the ecosystem using React has generated another 10M+ sites. You might flip that order, and do a massive campaign to get developer mind share, and supplement it with paid integrations in to the library ecosystem. We might even see new business models where framework and library authors pay tooling providers to include their framework in system prompts. But even then, you’re fighting against entrenched patterns in both React libraries AND LLM training data.</p><p>This isn’t about React being the best tool or that it’s Model is good for LLMs (I don’t see any evidence there at all). It’s about React being past the point where network effects make alternatives viable.</p><p>Here’s what brought this home for me: Last week I used Claude to build a Chrome Extension using Chrome’s built-in prompt API. Claude dutifully wrote the entire extension, but used self.ai.languageModel-the API from 6 months ago. The current API is LanguageModel.create(), but that wasn&#39;t in the training corpus.</p><p>Add in the fact that it can take years of <a href="https://web.dev/blog/interop-2025">Interop</a> work to get a feature to the point it becomes “ <a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility#:~:text=Features%20listed%20as%20newly%20available%20work%20in%20at%20least%20the%20latest%20stable%20version%20of%20each%20of%20the%20Baseline%20browsers%2C%20but%20may%20not%20work%20with%20older%20browsers%20and%20devices.">Baseline newly-available</a>” and then another 30 months for it to reach a point where it’s “ <a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility#:~:text=Features%20listed%20as%20widely%20available%20have%20a%20consistent%20history%20of%20support%20in%20each%20of%20the%20Baseline%20browsers%20for%20at%20least%202.5%20years.">Baseline widely-available</a> “. By that time, the ecosystem has moved on, and the feature is competing against entrenched patterns in both React libraries AND LLM training data.</p><p>This is the new reality: <strong>If it’s not in the LLM training data, it doesn’t exist.</strong> Not for 12–18 months, at least not until the next model training cycle and not until enough examples exist in the wild to statistically matter.</p><p>Now apply this to frameworks:</p><ul><li><strong>Web platform APIs</strong>: 0–6 months of real-world usage before training cutoff</li><li><strong>New frameworks</strong>: 0–6 months of real-world usage before training cutoff</li><li><strong>React patterns</strong>: 10+ years of accumulated examples</li></ul><p>Today, if your framework or documentation isn’t in the training corpus of the LLM, then it won’t be output. If the system prompt of the tool a developer uses doesn’t have your API, library or framework, then it’s not in the output. And if the user of a tool doesn’t ask for a specific API, library or framework, then it won’t be output. Model providers are skewing it so the model prefers a certain style, or framework or library.</p><p>The same dynamic applies to new web platform APIs designed to replace framework features. Consider the typical pattern:</p><ol><li>Browser teams identify a common pattern in frameworks (e.g., CSS Nesting instead of Sass)</li><li>Multi-year standardization process begins</li><li>Feature ships in browsers</li><li>Developers; keep using the framework pattern</li></ol><p>Why? Because:</p><ul><li><strong>The LLM learned the old pattern</strong>: Sass has 15 years of examples; CSS Nesting has 1 or 2 years</li><li><strong>The framework already works</strong>: React developers use styled-components, Tailwind, CSS modules</li><li><strong>The ecosystem is built</strong>: Thousands of React component libraries use existing CSS patterns</li><li><strong>There’s no incentive to switch</strong>: The new platform feature doesn’t make the site better for users</li></ul><p>For example:</p><ul><li>People loved <a href="https://sass-lang.com/">Sass</a>, but you need a build-step, so we have <a href="https://developer.chrome.com/docs/css-ui/css-nesting">CSS Nesting</a>. However its rarely output because preprocessor patterns are more common in the corpus and also React developers already have CSS-in-JS solutions that LLMs know how to output.</li><li>Carousels are hard to build, so maybe we should have them as an intrinsic part of the platform. But there are <a href="https://flowbite.com/docs/components/carousel/#:~:text=Create%20a%20new%20carousel%20object%20using%20the%20options%20set%20above.">tons</a> of <a href="https://daisyui.com/components/carousel/?lang=en">libraries</a> that <a href="https://getbootstrap.com/docs/4.0/components/carousel/">create</a> great <a href="https://www.npmjs.com/package/react-multi-carousel">carousels</a> that are already in LLM training data.</li></ul><p>As an author of many sites, I love these features. CSS Nesting alone lets me structure my CSS in a way that I personally find easier to read and maintain. But it doesn’t really change the quality of the experience of the site for the person using my site. It doesn’t change the performance of the site. It doesn’t change how accessible my site is. It just makes it easier for me to write and maintain.</p><p>The only new platform features that matter are ones that <em>can’t be built in user-space</em>, like:</p><ul><li>Multi-page view transitions (new navigation capabilities)</li><li>WebGPU (fundamentally new compute access)</li><li>WebAuthN and PassKeys (security primitives)</li></ul><p>Everything else is competing against entrenched patterns in both React libraries AND LLM training data.</p><p>There’s at least 3 constituencies to consider here:</p><ol><li>The “head” businesses building on the web — The top 1000 sites take the lion’s share of traffic and revenue on the web, and we don’t see massive technology shifts in the top 1000 through to top 1 million because these are established sites with established teams and shifting technology is hard with often unclear benefit outside of potential improvements to product velocity. They are likely to be using LLM based tooling to help increase velocity, but they are not going to be switching frameworks or libraries lightly.</li><li>The “middle” businesses building on the web — The next 10 million sites are being built by small teams and individuals and will likely be using LLMs to build new sites completely and unless they prompt will use the defaults the tools output</li><li>The “long tail” — These are people who are not formal web developers who will use tools like Loveable, Replit, or even directly in a chat app. They may never need to look at the code, so what do any of these new APIs do to help them build better sites? and they represent the growth in the platform and we have an <a href="https://aifoc.us/transition/">opportunity for millions more people to deploy on to the web</a></li></ol><p>The people in groups 2 and 3 are the ones driving the growth in sites on the web and are unlikely to be building with these tools don’t know about Passkeys, WebAuthn, Web Components, CSS Nesting, View Transitions, or any of the other new features being added to the platform. They just want a site created that does what they need it to do.</p><p>The thing is, the normal people using the web don’t care about the tools, frameworks and libraries are not something that concerns a normal person using the web. What concerns people is the experience of using the page. Does it load quickly enough? Are the interactions smooth? Does the site actually do what I need it to do?</p><p>Today, if you are a company targeting developers in any of those categories (LLM or a tool that outputs code from an LLM), to not output React by default is to limit your potential audience as your competitors are serving the current demand.</p><p>Now consider the current working model for code-generating LLM tools which reflect the ecosystem that they are trained on. This means that any new API, framework or library has a large hurdle to get over in terms of being something that will be output by the tool. The fact that <em>any</em> new feature might not be in the training corpus <em>and</em> will not be prevalent enough to have its usage patterns and idioms ingrained in the training and by extension the output of an LLM should be a concern to the people building new platform features.</p><p>Looking at today’s trend of tools primarily outputting React code, the comprehensive ecosystem of user-space libraries can do almost everything from custom select boxes, specialized date components and everything else. I can’t see a world where a new platform feature is going to displace the libraries in use nor can I see a world where a new framework is going to displace React in the short to medium term-I really love what the Remix folks are doing with Remix 3 and I will keenly watch how it is adopted and how LLMs might pick this up to see how this post plays out in the real world. I’d love to see how long it takes for LLMs to start outputting Remix code without specific prompting or including docs in the context.</p><p><strong>For framework authors:</strong> Building a new framework is building a product that LLMs won’t output for 12–18 months minimum, that has no library ecosystem, that developers don’t know, and that companies won’t adopt. You’re not competing with React’s technical merits-you’re competing with React’s statistical dominance in every LLM training corpus and every tools providers preference for their customer.</p><p><strong>For platform developers:</strong> Developer experience features (syntactic sugar, convenience APIs) are competing against established React patterns in LLM training data. They will not be adopted at scale. Focus on fundamental capabilities that can’t be built in user-space. For features that browser developers are creating today, we need to take a long hard look at the benefits that they will bring to the user and not the developer. To that extent, many of the platform features ranging from Web Components through to syntactical changes are just not needed by the vast majority of people building sites in the coming years.</p><p><strong>For tool creators (e.g, IDEs):</strong> If you’re not outputting React by default, you’re limiting your addressable market. Your competitors are serving current demand. You can’t afford to be principled about framework diversity.</p><p>Dead framework theory isn’t about frameworks dying. It’s about new frameworks being dead on arrival in a world where React has become the platform (at least as long as people need to maintain code.)</p><p>As an industry we should absolutely innovate and build new frameworks, libraries and platform features. We need innovation to push the web forward and create competition. But we need to be aware of the dynamics at play and have clear strategies to get our work into LLM training corpus, system prompts, and developer minds.</p><p>If the industry continues its current focus on maintainability and developer experience, we’ll end up in a world where the web is built by LLMs using React and a handful of libraries entrenched in the training data. Framework innovation stagnates. Platform innovation focuses elsewhere. React becomes infrastructure-invisible and unchangeable.</p><p>But here’s the optimistic take: If LLM usage continues to grow, tooling vendors will have to compete with each other on this homogenized ecosystem. When everyone outputs React by default, you can’t differentiate on framework choice. You have to compete on output quality. Market forces shift the focus from developer experience to user experience.</p><p>For either scenario, we need to start competing on user outcomes. I really want to see Evals and Benchmarks that focus on quality outcomes like Core Web Vitals did for performance. When tools compete to attract users, the ones that output meaningfully better experiences will win. This competitive pressure will incentivize the entire ecosystem to optimize for users, not developers.</p><p>And if we succeed? Then in the long run, <a href="https://paul.kinlan.me/will-we-care-about-frameworks-in-the-future/">the framework will become irrelevant</a> as the models improve to the point people <a href="https://blog.almaer.com/english-will-become-the-most-popular-development-language-in-6-years/">manipulate sites by words alone</a> and the LLM providers believe they can create better outcomes with their own frameworks or tuning (hat tip to Ade). The delivery technology becomes an optimized compiled output that meets user needs-whether it’s “React” or something else stops mattering.</p><p>As for the raw, naked, web platform? Focus on fundamentally new capabilities-the things that can’t be built in user-space, or where there’s clear user-experience benefit that can’t be achieved with libraries.</p><p><em>Originally published at </em><a href="https://aifoc.us/dead-framework-theory/"><em>https://aifoc.us</em></a><em> on October 12, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=97c2904012f4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[embedding]]></title>
            <link>https://medium.com/@paul_kinlan/embedding-cbc80872cead?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/cbc80872cead</guid>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Wed, 28 May 2025 10:59:48 GMT</pubDate>
            <atom:updated>2026-06-10T14:56:00.661Z</atom:updated>
            <content:encoded><![CDATA[<p>No, not that <a href="https://huggingface.co/spaces/hesamation/primer-llm-embedding">one</a>. The E in <a href="https://paul.kinlan.me/slice-the-web/">SLICE</a> is for embedding content&amp;mldr;. Oh wait, that E was for ephemeral. Hmm, never mind. I guess this is the other E which is actually the C in SLICE. “Composable”. After <a href="https://aifoc.us/a-link-is-all-you-need">linking</a> composability is one of the most critical features of the medium that is the web. It’s the only platform that I know of that enables expression by integrating live content from nearly any other site or service directly into the UI. Yes, we have APIs, but it’s the &lt;iframe&gt; and &lt;embed&gt; that have helped to make the web unique.</p><p>There’s a story that I heard before joining Google, that our very first developer API was a way to embed Google Maps into your website. It wasn’t that we invented an API, it was that the web made it easy to pull content from other sites and embed it into your own and lots of people wanted to do this. While embedding of maps is declining (according to BuiltWith), anywhere between <a href="https://trends.builtwith.com/mapping/Google-Maps">12–15% of the top 1 million sites</a> still embed Google Maps.</p><p>In “ <a href="https://aifoc.us/a-link-is-all-you-need/">A link is all you need</a>” and <a href="https://aifoc.us/super-apps/">super-apps</a> I touched on the ability for LLMs to create or recall content on the fly and how it’s potentially a huge shift for how we think about the web and in <a href="https://aifoc.us/ai-powered-site-mashups/">AI-powered site mashups</a> Andre Bandarra suggests that Agents will be able to create the ultimate mashup or sites and services because they attempt to solve for the user’s goal.</p><p>If the only thing really stopping this is the <a href="https://aifoc.us/latency/">latency</a> of the LLMs to generate the UI then it is a “when” and not “if” question. We really need to think about some of the downstream implications of this.</p><p>One extreme is where there is a <a href="https://aifoc.us/super-apps/">super-app</a> and it is the agent that can do everything for the user, generating content and UIs on the fly to fulfil a goal. Where does the web sit in this? The web is a legacy fallback and it’s not the web I want to see. Is there a possibility that an exchange of value could happen between the site and super-app? I believe that many site owners and businesses would want some way of keeping their brand, or enabling specific actions like up-sell on checkouts, so could we embed some functionality that brings my brand or service in front of the user in any site or app, including the super-app? Maybe it’s a checkout form, or a registration page, or, well anything that needs a user-action.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/469/0*ZuvqOBz5sVI5Nx1A.png" /></figure><p>Fictional example of embedding existing web functionality into a ‘chat app’</p><p>In 2020, while on my team, Jason Miller documented the <a href="https://jasonformat.com/islands-architecture/">Island architecture</a> (first proposed by <a href="https://sylormiller.com/">Katie Sylor-Miller</a>). At the time it felt a logical extension to “ <a href="https://web.dev/learn/pwa/architecture/">AppShell</a>”: Here’s some static code and here’s the dynamic bit — which on a technical level <em>is</em> what it describes, but at an architectural level it is something rather different. Islands are a way to think about how to compose your web app in to different bits of functionality. While still nascent, frameworks like <a href="https://fresh.deno.dev/docs/concepts/islands">Fresh</a> and <a href="https://docs.astro.build/en/concepts/islands/">Astro</a> have adopted this idea, it’s still just a framework-level concept and not a platform-level primitive.</p><p>When I look at the extreme that is “the super-app”, it feels like embedding and composability need to be key parts of the future of the web, and it needs to be something that developers and businesses can opt-in to and control to their brand and experience to as much as an extent as possible.</p><p>Now there is a natural reaction: Well, I don’t want super apps or LLMs. The technology is now here and it’s being used for good and for bad, and as I learnt from the desktop to mobile <a href="https://aifoc.us/transition/">transition</a>, the answer is to differentiate and not follow. Lean in to the areas that other platforms can’t compete on.</p><p>So, how does the web differentiate then?</p><p>One area that is ripe for innovation is the act of hyper-linking. We should actively investigate hyper-embedding (also known as transclusions). That is, we need to go beyond just being able to embed a site in a page ( iframe) or an API ( fetch()), or just merely linking to something (&lt;a&gt;), but instead enable the seamless embedding of functionality that is useful and composable and secure.</p><p>The boundary between functional components as described in islands offers so many opportunities for the web. By exposing islands/components/widgets to the browser in a way that it understands that a) there is something that is embeddable, b) what it can do, and c) how to talk to it, all while ensuring there can be security and privacy boundaries between the islands if required, could enable:</p><ol><li>A cleaner separation for site authors for use across their site. Islands and functionality across the current sites, and then render them in the page. Because the browser understands the intent of islands it enables page-level actions, automations, and chat-bots by the browser to help the user interact with the page.</li><li>Deeper integrations across sites. Developers have a habit of injecting any and all 3P JS into the page. A new primitive could separate the pages, ensure memory safety, and data-leakage while enabling even more composability across sites.</li><li>Native apps, or other agents to load these islands from other sites, and then render just the island inside their app.</li><li>There could be a marketplace and discovery mechanism for functionality for any given island’s intent and any given contract.</li></ol><p>This might sound like Web Components, but we don’t have clear contracts or cross-platform embed-ability. It’s something that I started to think about in <a href="https://paul.kinlan.me/custom-elements-ecosystem/">Custom Elements Ecosystem</a> but at the time there wasn’t a clear need for it. Now there is.</p><p>It might also sound like an &lt;iframe&gt;, but these are too heavy. I might just want to embed a small bit of functionality like a checkout form, or a map that has all my own branding.</p><p>It might also sound like the &lt;portal&gt; element which was meant as a more privacy-preserving &lt;iframe&gt; element, but again it&#39;s too high-level and doesn&#39;t allow for the embedding of functionality at a level that smaller than a page.</p><p>It might also look like <a href="https://paul.kinlan.me/what-happened-to-web-intents/">Web Intents</a> but this was a page level and not at a component level (and it got pulled out of Chrome).</p><p>We are in the start of an era where the <a href="https://paul.kinlan.me/the-headless-web/">web will be headless</a> <em>and</em> we don’t have the correct primitives to enable the web to be composable in a way that is useful and for it to thrive.</p><p>It’s too early to prescribe solutions, but I do believe that a way to define ‘islands’ or web components as embeddable and a way to embed sub-trees and components (islands) is needed and as we do that, I think it’s time again to think about exposing intents and contracts on sites, pages and components and make them discoverable.</p><p>My hope is that the designers of the web-platform, that is browser vendors and participants of the W3C, should be imagining what the platform could and should look like and how it should continue to differentiate itself in the future, and these are just a couple of early opportunities that I see.</p><p><em>Originally published at </em><a href="https://aifoc.us/embedding/"><em>https://aifoc.us</em></a><em> on May 28, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cbc80872cead" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A link is all you need]]></title>
            <link>https://medium.com/@paul_kinlan/a-link-is-all-you-need-0b60487d691b?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/0b60487d691b</guid>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Sat, 17 May 2025 19:59:17 GMT</pubDate>
            <atom:updated>2026-06-10T13:06:00.680Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><em>I’ll keep playing here while the rest of you flirt with apps. I’ll be here when you come back. I know it’s going to happen. Here’s why.</em></blockquote><blockquote><em>Linking.</em></blockquote><p><a href="http://scripting.com/stories/2011/12/13/whyAppsAreNotTheFuture.html#p11405">Dave Winer — 2011</a></p><p>The web has a lot going for it. We coined the term <a href="https://paul.kinlan.me/slice-the-web/">SLICE</a> (Secure, Linkable, Indexable, Composable, and Ephemeral) to describe its benefits, but at its purest essence the hyperlink is the thing that makes the web the web. It’s a thing of beauty. It’s why I fell in love with the Web. Click. Something new! It’s why I still love the web and it’s the thing that is unique to the medium because the web platform in a lot of cases has a thing at the end of it, yes there is link-rot, but you don’t have to install anything to get it running.</p><p>I decided to look for the genesis of the phrase “The web will always win” and it turns out the earliest quote still indexed is a post linking to Dave Winer’s post where he doesn’t actually directly say it (it’s implied).</p><p>But there’s something bugging me. It’s the over-confidence of the industry that the web will weather any storm. “The web will always win” is often quoted whenever someone poses that there is an existential threat to the web and the other side doesn’t think any of the proposals to address the challenge are needed.</p><p>Mobile and the rise of native apps was one of those challenges that I believe was a potential extinction-level event for the web. The web as experienced by people didn’t work well on mobile and people looked for Apps (they were told it, “There’s an app for that”). At the same time billions of people were getting their first computing experience through a mobile device and the web wasn’t something they had grown up around, it just didn’t even occur to them as a thing that they should do.</p><p>Yes, Apple through Safari introduced many technologies that would let the web work well on mobile (e.g viewports, touch, multi-touch, media queries are probably the biggest innovations at the time) but web developers just didn’t shift to match the expectation people had from their mobile devices.</p><p>We needed a change in how we thought about the web and how it should be used for this new context. From my own personal experience, it wasn’t until a Google “mobile-first” push in 2015 that you really started to see a change in how the web was experienced on mobile.</p><p>As I look at my own usage of LLMs today, there is a change in how I use the web and I am uneasy, but it wasn’t until recently that I was able to put my finger on it. Yes, as the cost of creating content drops because people use LLMs it enables a lot more low-quality content to be created and at the same time it is also enabling a lot of good experiences to be easily created (the structure of this very blog was made using an LLM), but the only way you get to discover and experience content is if you can navigate to it.</p><p>I was chatting with a colleague about the intersection of AI tooling and the web and the following thought popped into my mind “If you had a machine that could instantly recall or create any facet of information, do you need a link?”</p><p>It’s the link that I am worried about and I now think about this constantly.</p><p>The way that we — the people who build sites — create links is as a way of saying “I think this is important”. “I think this is related or has more context”. “I think you should look at this”. And what we know as a hyperlink, a thin blue line underneath some text made by wrapping it in an &lt;a&gt; tag connecting two documents together via a directed graph structure is just a construct of the technology that renders web pages.</p><p>What I am experiencing doesn’t feel a million miles away from the original definition of hypertext. The idea that you could have a machine that could recall any piece of information and then connect it with any other piece of text. Specifically, <a href="https://en.wikipedia.org/wiki/Transclusion">Transclusions</a> feel pretty close to where I see LLMs going.</p><blockquote><em>In computer science, transclusion is the inclusion of part or all of an electronic document into one or more other documents by reference via hypertext. Transclusion is usually performed when the referencing document is displayed, and is normally automatic and transparent to the end user.[1] The result of transclusion is a single integrated document made of parts assembled dynamically from separate sources, possibly stored on different computers in disparate places. </em>The result of transclusion is a single integrated document made of parts assembled dynamically from separate sources, possibly stored on different computers in disparate places.</blockquote><p>Emphasis mine — <a href="https://en.wikipedia.org/wiki/Transclusion">Transclusions</a></p><p>It feels like the directed edge that defines a link in this massive directed graph that we know as the web is changing as LLM’s seem to be able to connect concepts across many documents and just merge them into the response.</p><p>This directed graph nature of the web has been fundamental to how we experience the web. It enables things like Page Rank to exist, which to my understanding has the link imply some level of authority. Its unclear to me if this is of any importance to a LLM. Is the link just a way to point a web-crawler to another page so it can be ingested? If so, then is the only way to have an LLM not ingest your content is to make it undiscoverable? Maybe it’s to put it behind a login wall. This feels like a big step backwards on both fronts, however with things like Substack and Medium, the latter seems to be the ways it’s going.</p><p>The link can still point to content in the open or content that is private behind a login and enable instant access to any experience. So we still have that for now, but in a world where <a href="https://aifoc.us/super-apps/">LLMs become the super app</a> because they can recall and generate content and functionality in an instant, then what next?</p><p>I’m not sure if it’s a technology problem (i.e, links need to change) or if like the mobile-first push we <em>just</em> need to work out where the web fits in the grand scheme of things, but I don’t believe that the “Web will always win” is a good enough answer.</p><p>Maybe we always just need to ask LLMs to include citations. Maybe we need to redefine what a link is. Maybe we need to rethink the capabilities of the platform. Maybe we need to create more incentives for putting content on the open web and linking to it directly.</p><p>This is something that I want to explore more in the future and I don’t want to sit idly by and react in 5 years like we did with mobile. I want to be proactive and help shape the future of the web.</p><p><em>Originally published at </em><a href="https://aifoc.us/a-link-is-all-you-need/"><em>https://aifoc.us</em></a><em> on May 17, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0b60487d691b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[super-apps]]></title>
            <link>https://medium.com/@paul_kinlan/super-apps-0e464d8a3bed?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/0e464d8a3bed</guid>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Mon, 12 May 2025 12:06:27 GMT</pubDate>
            <atom:updated>2026-06-09T11:56:00.839Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve spent two weeks in April wandering around Japan with my wife, daughter and parents — it was incredible. I used a browser twice. Most of the time that I spent with my phone wasn’t on the web or in traditional apps, it was in a LLM.</p><p>I would give the LLM photos of packets of food and ask what it is and it would handily tell me the brand, and then I could follow up and with a photo of the back of the packet and ask if there was milk in it (my daughter can’t drink milk), and it would explain the ingredients and potential allergens&amp;mldr;. Given my non-existent ability to read Japanese I had to trust the LLM.</p><p>We went to Kyoto and I would ask the LLM what was written on the noticeboard of the shrine and what the cultural importance of it was, and it would tell me. I sat on a tourist train trundling through a valley pointing my camera at something that looked like a nuclear reactor — turns out it was a stadium.</p><p>While waiting outside a pharmacy I would ask the LLM to tell me the latest news about the local area and it would present a quick overview of what had happened in English from Japanese sources.</p><p>As I wandered around Himeji castle and had questions, I could just check the LLM. As I saw the fish gargoyles that adorn each of the roofs on the castle, I could ask what they were and their cultural relevance and get a comprehensive answer. A lot more than I could from the placards I dotted around the site.</p><p>As we were riding the Shinkansen back to Tokyo I wondered how many sites were blocking LLM’s User-Agents, so I asked Gemini to build a script that would check (and copied it into my Android Linux terminal — it didn’t work, python wasn’t installed, but it was so close).</p><p>I rarely left these tools and it’s been on my mind a lot.</p><p>A couple of months ago I had two ideas running around my head. The first was me musing if it was possible to have a future programming language built around prompts, and the second was will it be possible to build UIs based on a goal. Combining the two ideas I created a little toy-library called f and a demo that would build a UI for a site based on a request to a JSON API using as plain-english as I can currently get. I was blown away by how far you can get by describing your goals. Want a form that collects data? Great, just describe it! Want a UI built for a random API? Just point it at the data and ask it to build the UI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QUsJmrffLFPpMoeB.png" /></figure><p>Dynamically generated UI from prompts</p><p>So why is Japan and ‘f’ in the same article?</p><p>I had to work for two days on this vacation and I was relating my use of Gemini and ChatGPT to a friend about how every time I go to China show me how pervasive WeChat is across people’s lives. Gemini and ChatGPT were my super-app. Yes, on this trip I wasn’t ordering food, cars, laundry or anything else, but for my needs both Gemini and ChatGPT gave me everything I needed. Translations, background information, local-news, and even a bit of work that I needed to think about.</p><p>In that conversation, I was describing the experience of how I took a photo of a nuclear reactor and ChatGPT built a little program that scanned and panned the image to find where I was and what I was looking at. It built a mini application to solve the problem (see below) and it hit me&amp;mldr;.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/0*T2PBFkDp5F0zBMe_.png" /></figure><p>Chat GPT thinking and building — part 1</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/0*XtHPe-u318fzF2t1.png" /></figure><p>Chat GPT thinking and building — part 2</p><p>We’re not far away from tasks, be it expressed via text replies and “thinking tokens” or dynamic UI’s and applications that are built to service a single user requests from directly inside the LLM.</p><p>I started to describe this in “ <a href="https://paul.kinlan.me/the-disposable-web/">The Disposable Web</a> “, that it is becoming easier to create software that solves one problem once. And when I compare what can be created today in the canvases of these tools against many of the run-of-the-mill CRUD style experiences that operate inside WeChat and it feels easy (for me at least) to draw a connection that we are not far away from getting these applications built dynamically inside a LLM to service the need for the user, and when that happens, what’s next for the web?</p><p>Many of the apps inside WeChat are not incredibly complex, they are run-of-the-mill CRUD style experiences. We’re really not far away from getting these built dynamically and when that happens, what’s next for the web? I can see a straight line between WeChat and the experience I had in Gemini and ChatGPT. Yes, the experience in ChatGPT took a while to create (according to the “thinking” timing, it was over 90 seconds) and today it is far too slow for applications as we know it. If we use the Jacob Nielsen research <a href="https://jakobnielsenphd.substack.com/p/time-scale-ux#:~:text=0.1%20seconds%20(100%20ms)%20creates%20the%20illusion%20of%20instantaneous%20response">“0.1 seconds (100 ms) creates the illusion of instantaneous response” (2025)</a> as a target, then it looks like we have to make a 100 fold improvement to token generation to build blocking-free UI. How many Tokens/s do we need to make things feel instant? I think that might come sooner rather than later, model improvements seem inevitable and hardware improvements like Groq are showing that there is already a path.</p><p>HTML, CSS and JavaScript are the most expressive languages available today to render a UI and LLMs are pretty good today at generating them, so to me there is a world where this will be the easiest route to build UI that will service the specific needs of a user request directly in one of these LLMs and you rarely ever need to leave.</p><p>If you combine this with Agent communication protocols like MCP which are changing the way that we chain more complex apps and tasks together, it really feels like we are at the start of a major <a href="https://aifoc.us/transition/">transition</a> in computation and user interaction.</p><p>I argue pretty strongly that the web’s super power is the link. It lets anyone click on it and then navigate to an experience. App platforms would kill for this power because today their restrictive review process and restriction on what can run in their sandboxes (there are limits to what they allow developers to run dynamically — e.g, iOS relatively recently allowed non-browsers to run dynamic JS). Tools like ChatGPT are doing an end-run around this restriction and to me the power of the link is in question. Who needs a link anymore when you can recall any text or will any experience into existence?</p><p>HTML and JavaScript are the most expressive languages to render a UI. It’s not a stretch for a UI to be created to service the specific needs of a user request, or when an Agent wants some form of human input. If any application can do this, then what is the future for web apps? Will apps just live inside these apps like mini-apps live inside WeChat? Should the Web Platform engineers at browser companies invest more in in-app experiences (i.e, WebView)? Should we as an industry do more to enable any website in the browser to do the same?</p><p>There’s a lot the web can already do to be the primary platform for this type of experience&amp;mldr; We have sand-boxing for arbitrary code execution be it JS or any other flavour with WASM. We have the ability to run arbitrary code in a webview, and we have the ability to run arbitrary code in a web worker away from the UI. I wonder if there will be a future where we pull in small parts of existing web app’s DOM and run them inside a new super-app, <a href="https://paul.kinlan.me/custom-elements-ecosystem/#:~:text=Platforms%20as%20the%20decider%20of%20the%20component%20suite">or even define custom-element contracts</a> that will enable us to load widgets from other sites and enable “my” UI to be surfaced in the app.</p><p>I actually don’t know where the future will go, but the recent experiences that I’ve had lean me towards Gemini or ChatGPT being a new type of <a href="https://paul.kinlan.me/the-headless-web/">headless web</a> and I don’t think we are far away from having the everything app for the west?</p><p>Who needs a browser anymore?</p><p><em>Originally published at </em><a href="https://aifoc.us/super-apps/"><em>https://aifoc.us</em></a><em> on May 12, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0e464d8a3bed" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[transition]]></title>
            <link>https://medium.com/@paul_kinlan/transition-aaf5dfdd915b?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/aaf5dfdd915b</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Fri, 09 May 2025 19:05:47 GMT</pubDate>
            <atom:updated>2026-06-08T17:39:25.061Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5sISXKBdGAGmU55n.jpeg" /></figure><p>I remember the exact moments when major transitions in my life happened. The first time I got a BASIC program working. The first time someone used one of my programs. My first email. The first time I visited a website. The first time I made a website. The first time I saw my future wife. The first time I held each of my children.</p><p>In each of these moments, I knew things changed and at the same time I had no comprehension of how they would change the direction of my life. It is like there was a fog in front of me, I could see vague outlines of things.</p><p>When I look back on my career as a Web Developer, I’ve been involved in many major transitions: The Web being a thing that you had to care about; Dial-up to Broadband enabling a step-change in the types of experiences we could interact with; The Desktop to Mobile transition and all the change that this brought for the web — At Google we felt that we had to go bring the web to mobile, so I focused on Mobile-first as a primary motivator, then a push on Progressive Web Apps as the way that all apps should be built and experienced.</p><p>Through these later transitions I felt I could clearly see the path that I or my teams should take. Getting my hands on an iPhone and instantly it felt clear to me that the web needed to work well on mobile because this is where the future will be. Later given the growth of mobile and the fact that for billions of people it was their first experience with a computer it was quickly obvious. It felt clear that there was an existential risk to the web and that there would need to be a solutions for the centralization of Apps — which our solution would become “Progressive Web Apps” — it all felt pretty clear.</p><p>But each of these transitions to the web wrought a lot of change.</p><ul><li>Dial-up to Broadband — The web became a desktop-class hosting platform for applications and fundamentally changed how Windows worked, leading to the rise of Web aggregators like Google, and the creation of services like YouTube, all because the increase in bandwidth and reduction in latency meant that you could do surf more and developers could deliver more. Many of the services we once relied on (anyone remember MapQuest?) were obliterated nearly over night by more interactive and engaging services. Always being on meant always having access to email, instant messaging, social, Flash games, audio and video.</li><li>Desktop to Mobile — You could see the web just not working well on iPhone-like mobile devices, and while it took a bit of time for Apps to find their mobile-first footing (i.e, not just a port of the Desktop experience) the web clearly took a lot longer to be mobile-first, or even responsive. Yes, Responsive design had been around prior to the launch of the iPhone, but it wasn’t <a href="https://paul.kinlan.me/future-of-web-on-mobile-coldfront-conf/#:~:text=Late%20last%20year,not%20%22Mobile%20Friendly%22.">until 2015</a> when Google search started a mobile-first push that the ecosystem really started to move. My own involvement in this project started a lot earlier as I could see an explosive growth of mobile because of reductions in price of the device and massive improvements to connectivity in India and China meant that people’s first computing devices might never highlight the web.</li></ul><p>Some people see the transition far ahead of time. I had friends who built a web browser for Windows CE because they saw the rise of Nokia phones and could see the next jump to a more powerful experience and wanted to ensure that web worked well on these devices. I didn’t see that then, I just saw terrible WAP sites. However, I could feel the change with the iPhone.</p><p>It was the same with LLMs. The first time that I used an LLM was in the OpenAI playground&amp;mldr; I believe it was GPT 2, and I just didn’t think that this was going to be a fundamental shift in the way that we interact with computers. I couldn’t see how this was going to change the way that we interacted with the web. I didn’t see how this would change how I build software almost overnight. I just couldn’t see how this was could change the way that we interacted with our devices.</p><p>I remember thinking: “That’s a neat trick”. And then I got on with my life.</p><p>Like many people, I think this changed when we first played with ChatGPT. It wasn’t perfect, it was slow, but I got it building a simple web app in a few minutes and I could feel that this had the potential to change the way that I work and the way that people interact with computers. I remember clear as day, sitting next to my wife on the couch saying that everything is going to change.</p><p>It feels clear that we are in the midst of another major transition, and I’m at a personal transition. I’m at a point where I was thinking about what I want to do next with what I think will be the next huge shift for computing. I’ve been working on the web nearly 30 years and working at Google for 15 of those and my fundamental question is: What is going to happen to the web?</p><p>What I do know is that I love the web, I think it’s the best platform to write once and reach everyone. I love seeing that the web is the place where people are experimenting with the entire range of delivery of AI experiences to people, be it access to Large Language Models, Image Segmentation, Video analysis, or even new flavours of search.</p><p>I want to be at the forefront of the medium that is the web, and the potential new platform that is “AI” (we need a much better name) and <a href="https://aifoc.us/">aifoc.us</a> is my own personal place to muse on a lot of questions about this transition.</p><p>ps, Blame Barry Pollard for this domain name.</p><p><em>Originally published at </em><a href="https://aifoc.us/transition/"><em>https://aifoc.us</em></a><em> on May 9, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aaf5dfdd915b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[domcurl: curl + JavaScript]]></title>
            <link>https://medium.com/dev-channel/domcurl-curl-javascript-1dfd2f81aab?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/1dfd2f81aab</guid>
            <category><![CDATA[headless-chrome]]></category>
            <category><![CDATA[puppeteer]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Mon, 19 Mar 2018 10:49:24 GMT</pubDate>
            <atom:updated>2018-03-19T10:49:24.957Z</atom:updated>
            <content:encoded><![CDATA[<p>For a long time I’ve been thinking about what the future of the web looks like when we go past what we know as the traditional web browser. I called this <a href="https://paul.kinlan.me/the-headless-web/">The Headless Web</a> and I wanted to answer was “What if everything was powered by ‘The Web’, but you never saw a browser?”. Specifically I believe that if you have access to a full browser, but no visible to “chrome” then there is a huge opportunity for a new set of services.</p><blockquote><em>Using the browser as a service is an incredible opportunity. It allows us to take the declarative HTML and combine it with the developer defined procedural execution of JavaScript and run deep analysis on the content.</em></blockquote><blockquote><em>…</em></blockquote><blockquote><em>Running a browser on the server will allow us to more easily build services which parse data that is generated dynamically, it will allow us to more easily us run our own logic against the logic in a page (form fill as an example) and I believe that it will open up the ability to more effectively run actions against data embedded on the page.</em></blockquote><p>It’s taken a while, but I think we are getting there.</p><p>I’m enamored by <a href="https://developers.google.com/web/tools/puppeteer/">Puppeteer</a>. Puppeteer is a JavaScript library that sits on top of the Chrome Dev Tools protocol and it allows you to automate and script the Chrome browser.</p><p>My day-to-day work involves a lot of debugging web servers and ensuring. Like many developers I use curl to make requests to a web server and check the response. It’s an amazing utility, however in today’s world many developers are building sites that are built using a lot of JavaScript and this makes it impossible to inspect the complete response.</p><p>I decided to create a cUrl-like utility for fetching a resource and running the JavaScript on the page called <a href="https://www.npmjs.com/package/domcurl">domcurl</a>.</p><p>domcurl is a small NodeJS application that uses Puppeteer and can be installed by issuing the following command: npm i domcurl. Like the curl command you can issue a simple domcurl [url] to fetch the resource and run the JS on the page.</p><p>It doesn’t replicate all of curl, but it is quite featureful with the following features.</p><ul><li>Specify a url to fetch. i.e, domcurl [url]</li><li>Inspect the response headers with -v. i.e, domcurl -v [url]</li><li>Set cookies with -b i.e, domcurl [url] -b &quot;test=hello; Domain=airhorner.com; HttpOnly;&quot; -b &quot;hello=world; Domain=airhorner.com; HttpOnly;&quot;</li><li>Add custom headers using the -H argument.</li><li>Manually set the STDOUT with -o and STDERR with --stderr</li></ul><p>I’m finding it quite useful eventhough it can’t stream the results like curl can because it has to wait for the CSS and JS to be downloaded and executed.</p><p>I also took the liberty to add a couple of extra features that are specific to JavaScript and Chrome.</p><ul><li>Output a Chrome Dev Tools trace file (including screens hots.) domcurl --url https://example.com --trace test.json</li><li>Include it as a JavaScript module if you have the need to integrate it into any of your existing applications.</li></ul><pre>const {domcurl} = require(&#39;domcurl&#39;); domcurl(https://medium.com/%60https://paul.kinlan.me/%60,%20%7B%7D);</pre><p>Whilst this tool is more of a demo than a full service, I think <a href="https://paul.kinlan.me/the-headless-web/">The Headless Web</a> is maturing and tools like Puppeteer and others are going to help us realize the continued power of the web. We just need to build for it.</p><p><em>Originally published at </em><a href="https://paul.kinlan.me/domcurl/"><em>https://paul.kinlan.me/domcurl/</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1dfd2f81aab" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dev-channel/domcurl-curl-javascript-1dfd2f81aab">domcurl: curl + JavaScript</a> was originally published in <a href="https://medium.com/dev-channel">Dev Channel</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Web is my API]]></title>
            <link>https://medium.com/dev-channel/the-web-is-my-api-1e925fae4964?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/1e925fae4964</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Thu, 28 Sep 2017 10:40:46 GMT</pubDate>
            <atom:updated>2017-09-29T08:38:11.421Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="http://softwareas.com/">Michael Mahemoff</a> taught me a lot about the possibilities of the web. Prior to working with Mike I built on the web and I understood the benefits such as linkability and discovery, but I never really had a full picture of what would be possible.</p><p>One thing that Mike said was “<a href="http://softwareas.com/cors-scraping-and-microformats/">the Web is my API</a>”, where he talked about the being able to expose your site and your data in a page via microformats and other structured data and being able to access it directly from another another browser context, using a simple XMLHttpRequest and the CORS API:</p><blockquote><em>Anyway, what’s cool about this is you can treat the web as an API. The Web is my API. “Scraping a web page” may sound dirtier than “consuming a web service”, but it’s the cleaner approach in principle. A website sitting in your browser is a perfectly human-readable depiction of a resource your program can get hold of, so it’s an API that’s self-documenting. The best kind of API. But a whole HTML document is a lot to chew on, so we need to make sure it’s structured nicely, and that’s where microformats come in, gloriously defining lightweight standards for declaring info in your web page. There’s another HTML5 tie-in here, because we now have a similar concept in the standard, microdata.</em></blockquote><p>It was around the same time that I was starting to work on <a href="https://en.wikipedia.org/wiki/Web_Intents">Web Intents</a>, the spirit of which was the similar — give users access to data and services from another origin — but it was a lot more complex. I wanted to enable discovery of services and then to interact with those pages. And Mike wanted to move the web to providing access to data and services. It stuck with me. <a href="https://twitter.com/Paul_Kinlan/status/913000817170534400">Even if I did forget the original attribution</a>.</p><p>I recently did a talk for Nordic JS where I highlighted that we don’t build really build truley interconnected services on the web, and when we do it follows a model of mostly server to server interactions. That is a web site will integrate with a 3rd party service by routing all API request through their server to the remote service and managing all the complexities that come with that.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Jd47FtqFCJXhbgsl.png" /></figure><p>It works, we have entire web built with this, but it can be incredibly complex when you consider authenticaion, authorization, transport protocols and differing RPC methods (REST, GraphQL etc). Mike was proposing something much more elegant, that with CORS enabled sites and a bit of JavaScript, we can talk directly to the remote service by using the site.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KgbwcYLreomSROnt.png" /></figure><p>There have been a couple of issues that cropped up in between. The primary issue is that even though CORS is widely supported in browsers, developers rarely use it. CORS is a protection that we need on the web but it’s hard to set up and debug, and the “Web as an API” has not really been pushed too much.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jyMa-EsQmFK91tLS.png" /></figure><p>We are moving to a world where sites are getting generated in the client with JS and sessions and state for the user are managed in the entirely on the client.</p><p>We still need the ability to communicate from our sites to a remote service, and I still strongly believe that we need to decentralize our integrations with other sites and apps, but the first thing that we need to do is connect our sites and apps together in away that is more than just a link. We need our sites to expose their capabilities and functionality directly to other windows on the users system.</p><p>Every website should be able to expose an API that the owner of the site is control of, directly to other clients.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JH7n-YAUHjYL5ZT1.png" /></figure><p>The good news is that we can already do it, we’ve had the primitives on the platform for at least 7 years (postMessage and MessageChannel), and forever since window.open, but we don’t use these tools to interact with sites for similar reasons why we don’t use CORS: It’s hard and it’s nearly impossible to define a sane API that is simple and consistent to use and that doesn’t require pulling in huge third party libraries for each service that you want to interact with.</p><p>The platform only allows you to communicate between sites using messaging passing which means that as a service owner if you wanted to create an API, you have to build a state machine that serializes messages in to some state, reacts to it, and then sends a message back to the client and then you have to create a library that does that for the developer consuming your service. It’s incredibly complex and convoluted and I belive is one of the primary reasons why we haven’t seen more adoption of Web Workers and client-side APIs.s</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HhFsfGhCBJTHe_7k.png" /></figure><p>We have a library that helps: <a href="https://github.com/GoogleChromeLabs/comlink">Comlink</a>.</p><p>Comlink is a small API that abstracts the MessageChannel and postMessage API’s in to an API that looks like you are instantiating remote classes and functions in the local context. For example:</p><p><strong>Website</strong></p><pre>// Set up.<br>const worker = w.open(&#39;somesite&#39;);<br>const api = Comlink.proxy(w);<br>// Use the API.<br>const work = await new api.Test();<br>const str = await work.say(&#39;Yo!&#39;); <br>console.log(str);</pre><p><strong>Web Worker</strong></p><pre>class Test { <br>  say() { <br>    return `Hi ${this.x++}, ${msg}`;<br>  }<br>} <br>// Expose the API to anyone who connects. <br>Comlink.expose({Test}, window);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-yw_nnvqOxwACgiH.png" /></figure><p>We expose an API on the service, we consume the API in the client via a proxy.</p><h3>Is there a better example?</h3><p>I built a <a href="https://rss-to-web-push.glitch.me/">site that subscribes to a pubsubhubbub endpoint and when it recieves a ping it sends a JSON message</a> to a user defined endpoint. I didn’t want to manage the push notification infrastructurion for this small app, another site I built (<a href="https://paul.kinlan.me/the-web-is-my-api/webpush.rocks">https://webpush.rocks/</a>) can do all that, I just want to use integrate with that service..</p><p>But how do I get the subscription URL (the piece of data I need to be able to send notifications) held in the client of webpush.rocks back into my site?</p><p>When I initially built this site, all you could do was let the user open the site and then copy and paste the URL between the pages. Why not just expose an API that any site could use? That’s what I did.</p><p>webpush.rocks defines an API called PushManager that has a single method on it subscriptionId. When the page loads it exposes this API to the window as follows:</p><pre>class PushManager {<br>  constructor() { } <br>  async subscriptionId() { <br>    //global var ick... <br>    let reg = await navigator.serviceWorker.getRegistration();<br>    let sub = await reg.pushManager.getSubscription();<br>    if(sub) { <br>      return `${location.origin}/send?id=${sub.endpoint}`;<br>    } <br>    else { <br>      return ``; <br>    }<br>  } <br>} </pre><pre>Comlink.expose({PushManager}, window);</pre><p>The API interacts with the PushSubscriptionManager API in the DOM and returns a custom URL to the calling site. The important thing here is that because it is running asynchronously I can wait for user verifcation that they want to perform the action (or not).</p><p>Back on the calling client site (the app that wants to get the subscriptionId). When a user clicks on the link, we obtain a reference to the window we just opened and hook-up our Comlink proxy to it. The service API is now exposed to our client and we can instantiate the PushManager API like it was a local service, but it is all interacting with the remote instance service in the other window.</p><pre>let endpointWindow = window.open(&#39;&#39;, &#39;endpointUrlWindow&#39;);<br>let pushAPI = Comlink.proxy(endpointWindow); <br>let pm = await new pushAPI.PushManager(); <br>let id = await pm.subscriptionId(); <br>// Update the UI. <br>endpointUrlEl.value = id;</pre><p>Here is a very quick video of what is happening. A very simple and light-weight interaction, it opens the service and then gets the ID it needs.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FvTYZXx31EHc%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DvTYZXx31EHc&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FvTYZXx31EHc%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/16d5487283609342b6f713c44b530d47/href">https://medium.com/media/16d5487283609342b6f713c44b530d47/href</a></iframe><p>As a service provider I have exposed a constrained set of functionality that is only available on the client to another site and I can secure it and ask for user consent at the same time before I return the data back to the user, all with a simple to use API.</p><p><em>The Web is the API.</em></p><p>Quite rightly, we don’t let sites inspect or manipulate the DOM or state of another origin, but I posit that if you have control over the services and functionality of your site and how users engage with it then you can expose the most important information and services to any client that wants to use your service securely (you are in control) and it allows you to:</p><ul><li>Focus on what you are good at.</li><li>Fast data transfer between sites and apps because it is all in the client.</li><li>IPC even when offline.</li><li>Run code in the origin context</li></ul><h3>What API’s should sites expose?</h3><p>This is something that I would like to explore more. I exposed some basic functionality to a Push Notifications service because that is the intent of the site, but the important piece for me was that I was in control of which parts of the DOM that I wanted to give back to other developers.</p><p>I would like to get to a place where every site can expose a consistent API to users and a way to discover other functionality and services.</p><p>Each site owner could expose just the core functionality to their service so that we could do CRUD based operations. We could have complex interactions.</p><p>We could get to a web where we have Unix like services that do one thing well and a user pipes them together all on the client.</p><p>Each site could expose a VDOM of a subset of the page that is defined by the service owner so that we have a consistent way to pull move data based on the DOM between sites securely.</p><p>I could imagine that we might want quick access to all of the schema.org based objects or other structured data on the page (they could be dynamically generated) like Mike did in his original post.</p><p><a href="https://github.com/GoogleChromeLabs/comlink">Comlink</a> gives us a way to expose and consume services quickly and easily on top of the platform primitives that have been around for years. We finally have a lot of the pieces in place that allow us to make the Web the API.</p><p><em>The Web is my API. Make it yours too.</em></p><p><em>Originally published at </em><a href="https://paul.kinlan.me/the-web-is-my-api/"><em>paul.kinlan.me</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1e925fae4964" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dev-channel/the-web-is-my-api-1e925fae4964">The Web is my API</a> was originally published in <a href="https://medium.com/dev-channel">Dev Channel</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reinventing Web Intents]]></title>
            <link>https://medium.com/dev-channel/reinventing-web-intents-ea6e8eff6aa4?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/ea6e8eff6aa4</guid>
            <category><![CDATA[html]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Wed, 27 Sep 2017 07:08:39 GMT</pubDate>
            <atom:updated>2017-09-29T08:38:52.511Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gzs_qcc13zQ4mp-jrgEAcQ.png" /></figure><p>I never got over the <a href="https://paul.kinlan.me/what-happened-to-web-intents/">death of Web Intents</a>. I always felt that there is still a serious problem on the web, we build <a href="https://paul.kinlan.me/unintended-silos/">silos</a> that lock the user into one web site and we don’t connect our apps together to build richer experiences. We have links that allow us to navigate to another site, but we don’t connect our apps to functionality that we can use in our sites. Be it picking an image from a cloud service to use in your app, or editing an image in the users preferred editor; we just don’t link our services the way we link our pages.</p><p><a href="https://en.wikipedia.org/wiki/Web_Intents">Web Intents</a> was a failed attempt to fix that. The <a href="https://paul.kinlan.me/navigator.share/">Share API</a> solves one use case for interconnecting sites and apps, but generally IPC and service discovery have never been solved and I think I have a solution… Ok, I don’t have a solution, I have an experiment that I am incredibly excited about.</p><p>Over the past couple of months Surma on my team and Ian Kilpatrick were working on a shim for the <a href="https://github.com/GoogleChromeLabs/tasklets">Tasklets API</a>. The Tasklets API was designed to allow a light-weight multi-threaded API’s to exist on the web. An ES6 class could be exposed as a ‘tasklet’ and you could call it without blocking the main thread — great for UI’s. The tasklet API by itself is very interesting, but the most interesting piece for me was that they built a Polyfill using a Web Worker and developed a way to expose the functionality of the ES6 class that was defined in the Worker. They had abstracted all of the complexities of the postMessage API away into a neat package and a sane model for JS developers.</p><p>One of the reasons that we built the Web Intents API was because the developer experience of creating an API and a service that worked with the postMessage API was incredibly complex, you have to deal with the postMessage API and then you have to manage a complex message processing system and associated state machine.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ETO06vbn-vCQU0vN.png" /></figure><p>It’s just complex. It get’s even worse if you want to have two windows interacting with each other. The window you open has to signal back the the opener that it is ready before you can start to send it messages. TL;DR - window.open opens about:blank before it navigates to the URL you define.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8_WQ3Z7sxSmVM6Kz.png" /></figure><p>It gets even more complex when you want to pass messages between multiple windows or workers in other windows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YuYZCvbP-BTonTs3.png" /></figure><p>I think this is one of the main reasons why people expose client side API’s. It’s too hard.</p><p>The tasklets polyfill had a solution buried inside of it and I cheekily asked Surma if he could refactor the tasklets API into a simple Proxy API, a couple of hours later out popped <a href="https://github.com/GoogleChromeLabs/comlink/">Comlink</a>. Comlink is a small API that abstracts the MessageChannel and postMessage API’s in to an API that looks like you are instantiating remote classes and functions in the local context. For exampe:</p><p><strong>Website</strong></p><pre>const worker = new Worker(&#39;worker.js&#39;); <br>const api = Comlink.proxy(worker); <br>const work = await new api.HardWork(); <br>const results = await work.expensive();</pre><p><strong>Web Worker</strong></p><pre>class HardWork { <br>  expensive() { <br>    for(let i = 0; i &lt; 1e12; i++) <br>      sum += /* …omg so much maths… */       <br>    return sum; <br>  }  <br>} </pre><pre>Comlink.expose({HardWork}, self);</pre><p>We expose an API on the service, we consume the API in the client via a proxy.</p><p>I think it is incredibly compelling and Comlink by itself has the ability to revolutionize the usage of Web worker by drastically improving the developer experience by providing a simple API for their team to be able to use.</p><p>Doing the same thing between windows is just as easy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lQQDIAUPcYk2X5UU.png" /></figure><p>But I had another thought… I can reinvent a small part of Web Intents was supposed: improve service discovery and make it easy for developers to interact with the services.</p><h3>Web Intents?</h3><p>One of the neat things about the Comlink API is that it automatically will try to use Transferable objects to pass data between the client and the service, and it turns out that MessagePorts are transferable. The idea that I had is that if I could create a simple API that is designed to just return a MessagePort based on some criteria (such as verb) then as the client, I wouldn’t care where that MessagePort came from.</p><p>Here is my thinking: I will have a site that acts as a middle-man and will maintain a list of services and where they live and will be able hook up clients who ask for types of services, kind of like so.</p><ul><li>A service site will be able to say to the middle man “I offer service X that works on data Y and lives at page Z”</li><li>A client site will be able to say to the middle man “I need a service that does X on this data Y. What do you have?”</li></ul><p>Mapping this back to a rough design, I need a Service that exposes two methods: register and pick.</p><p>register, will, well register the service with the middle man. pick on the other hand is a little more interesting and I’ve broken it down into a couple of steps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/920/0*43y21ht9Dmp_B9sc.png" /></figure><p>The flow isn’t overly complex when you dive into it. I crated a <a href="https://web-intents.glitch.me/scripts/service.js">basic wrapper that you include in every service and client application</a>. The wrapper handles the first interaction with the middleman and does some basic housekeeping by wrapping the complexities of opening a window to the service picker at ‘<a href="https://web-intents.glitch.me/pick%27">https://web-intents.glitch.me/pick&#39;</a>.</p><p>Once the picker is open it will find all the services that match the criteria the user needs, it will then present it to the user as a simple list. The user opens their preferred site and behind the scenes that site exposes it’s API back to the original client via the middleman. Finally, when the connection is complete and we are talking to the chosen service we can remove the middle-man.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TaA2d2nv_KY_I5SD.png" /></figure><p>The process actually is a little more complex than I am letting on. Under the hood we are passing a lot of MessagePorts between windows but the consumers of the API never see any of this complexity. The good thing is that that when the client and the service are connected and they talk directly via a nice service defined API and they don’t actually know who is on either end. Neat.</p><p>Below is a quick dive into the code to show how simple it is.</p><p><strong>Service</strong> (<a href="https://web-intents-service-1.glitch.me/">demo</a>)</p><p>The service is relatively simple, it has a class that interacts with the DOM and logs some output.</p><p>We expose the Test class to the ServiceRegistry and we offer a way to register the capabilities of this service.</p><pre>class Test { <br>  constructor() {} <br>  outputToPre(msg) { <br>    let output = document.getElementById(&#39;output&#39;);          <br>    output.innerText += msg + &#39;\n&#39;; <br>  } <br>} </pre><pre>let registry = new ServiceRegistry({ Test });<br>register.onclick = async () =&gt; { <br>  let service = await registry.register(&#39;action&#39;,&#39;*&#39;,location.href); <br>};</pre><p><strong>Client</strong> (<a href="https://web-intents-client.glitch.me/">demo</a>)</p><p>The client is simple, we create an instance of the registry and call pick.</p><p>pick connects to the middleman, and waits for the user to select the service. Once the user selects the service the middleman (ServiceRegistry) passes the API that the remote service has exposed to the client. We can then instantiate an instance of the remote API and invoke methods on it.</p><pre>let registry = new ServiceRegistry(); <br>let resolvedService = await registry.pick(&#39;action&#39;,&#39;image/*&#39;); <br>remote = await new resolvedService.Test(); <br>remote.outputToPre(&#39;calling from window.&#39;);</pre><p>I am quite pleased with this as an experiment. Here is a video of the Service Discovery and invocation of the above code.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F1igal-ehMB4%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D1igal-ehMB4&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F1igal-ehMB4%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/e6aa1d064f462094b405bf6b26c5d735/href">https://medium.com/media/e6aa1d064f462094b405bf6b26c5d735/href</a></iframe><p>Let me know what you think. Too much?</p><p><em>Originally published at </em><a href="https://paul.kinlan.me/reinventing-web-intents/"><em>paul.kinlan.me</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea6e8eff6aa4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dev-channel/reinventing-web-intents-ea6e8eff6aa4">Reinventing Web Intents</a> was originally published in <a href="https://medium.com/dev-channel">Dev Channel</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Detect if your Native app is installed from your web site]]></title>
            <link>https://medium.com/dev-channel/detect-if-your-native-app-is-installed-from-your-web-site-2e690b7cb6fb?source=rss-a7bf6b8f7cd0------2</link>
            <guid isPermaLink="false">https://medium.com/p/2e690b7cb6fb</guid>
            <category><![CDATA[progressive-web-app]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Paul Kinlan]]></dc:creator>
            <pubDate>Wed, 26 Jul 2017 17:42:04 GMT</pubDate>
            <atom:updated>2017-07-26T17:45:23.041Z</atom:updated>
            <content:encoded><![CDATA[<p>As the capabilities of the Web become more aligned with what was once the domain of native experiences there are an increasing number of times where a developer will want to reduce the confusion for their users who have both the web and native apps installed.</p><p>Take notifications for example, introduced in Chrome 42; they allow developers to easily re-engage with users who opt to receive messages. But what if the user also has their native app installed? There was no way for you as the developer to know if your user has your app installed on their current device. If the user has the app installed there might be no reason to prompt for notifications from the web as well.</p><p>In Chrome 59 we are introducing a new API called getInstalledRelatedApps(). This new API lets you determine if your native app is installed on a device.</p><blockquote><strong>Note:</strong> This API is behind an <a href="https://github.com/jpchase/OriginTrials/blob/gh-pages/explainer.md">Origin Trial</a>, this means that we are in an experimental mode and are actively looking for feedback. You have to <a href="https://github.com/jpchase/OriginTrials/blob/gh-pages/developer-guide.md">opt your site</a> into this trial because it is not available broadly on the web. You can now <a href="https://goo.gl/xxjW3L">sign up for the trial</a>.</blockquote><p>This is an incredibly powerful API because it gives you access to information that you can’t infer from the web. This means that there must be a provable bi-directional relationship between your site and your native app. There are three core components that make this work.</p><ol><li>There is a reference to your native app from your Web App Manifest via the related_applications property. This is your site saying that it is related to the native app.</li><li>There is a native app installed with the same package name as the one referenced in your Web App Manifest. The app must have a reference from your AndroidManifest.xml via the asset_statements element. This asserts that your native app has a relationship with your site.</li><li>If the above two criteria are met then a call to getInstalledRelatedApps() will resolve the list of apps.</li></ol><p>These three steps are in place to ensure that only you can query your apps and that you have reliably demonstrated ownership of the site and app. They are now described in more detail.</p><h3>Define the relationship to your native app in your Web App Manifest</h3><p>You need to ensure that you have a <a href="https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/">Web App Manifest linked to from your site</a>. In the manifest you must define a related_applications property that contains a list of the apps that you want to detect. The related_applications property is an array of objects that contain the platform on which the app is hosted and the unique identifier for your app on that platform.</p><pre>{<br>  ...<br>  &quot;related_applications&quot;: [{<br>    &quot;platform&quot;: &quot;play&quot;,<br>    &quot;id&quot;: &quot;&lt;package-name&gt;&quot;<br>  }],<br>  ...<br>}</pre><blockquote><strong>Note:</strong> Only Chrome on Android supports this, so the platform must be set to “play”. You also need your “id” to be the exact package name for your Android App.</blockquote><h3>Create the relationship to your site in your AndroidManifest.xml</h3><p>Next, you need to have your native app signal to the device that it is related to your Web App. You need to define the relationship with your site by ensuring that the app has the same package name as that defined in the Web App Manifest and that it also refers back to the website <a href="https://developers.google.com/digital-asset-links/v1/create-statement#android_app_statement_file">using the Android Digital Asset Links</a> infrastructure.</p><p>Creating the link back to your site is possible by adding the following to your AndroidManifest.xml:</p><pre>&lt;manifest&gt;<br>  &lt;application&gt;<br>   ...<br>    &lt;meta-data android:name=&quot;asset_statements&quot; android:resource=&quot;@string/asset_statements&quot; /&gt;<br>   ...<br>  &lt;/application&gt;<br>&lt;/manifest&gt;</pre><p>And then adding the following to your strings.xml resource, replacing the &lt;site-domain&gt; with the domain of your site:</p><pre>&lt;string name=&quot;asset_statements&quot;&gt;<br>  [{<br>    \&quot;relation\&quot;: [\&quot;delegate_permission/common.handle_all_urls\&quot;],<br>    \&quot;target\&quot;: {<br>      \&quot;namespace\&quot;: \&quot;web\&quot;,<br>      \&quot;site\&quot;: \&quot;https://&lt;site-domain&gt;\&quot;<br>    }<br>  }]<br>&lt;/string&gt;</pre><h3>Test for presence of the app</h3><p>Once you have the required metadata deployed on your app and on your site, you should be able to call navigator.getInstalledRelatedApps(). This returns a promise that resolves to the list of apps that are installed on the user&#39;s device that meet the above criteria (i.e., have been proved to be owned by the app developer).</p><pre>navigator.getInstalledRelatedApps().then(relatedApps =&gt; {<br>  for (let app of relatedApps) {<br>    console.log(app.platform);<br>    console.log(app.url);<br>    console.log(app.id);<br>  }<br>});</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/859/0*V1ipp_pMDxWUL2vo.png" /></figure><h4>Testing on localhost</h4><p>In your strings.xml set the &quot;site&quot; property value to be http://localhost:[yourportnumber] like the following.</p><pre>&lt;string name=&quot;asset_statements&quot;&gt;<br>  [{<br>    \&quot;relation\&quot;: [\&quot;delegate_permission/common.handle_all_urls\&quot;],<br>    \&quot;target\&quot;: {<br>      \&quot;namespace\&quot;: \&quot;web\&quot;,<br>      \&quot;site\&quot;: \&quot;http://localhost:8000\&quot;<br>    }<br>  }]<br>&lt;/string&gt;</pre><p>Ensure that your Web App Manifest has the correct package name for your locally installed App. When you deploy your Android App, make sure that you update the site property value to be the correct URL for your site. The most efficient way to manage this is through your product flavours (Release, Debug, etc.) which allows you to specify different resource files based on the build target, meaning that your Release target will only ever contain your live domain.</p><h3>Use cases</h3><p>There are many different ways that you can use this API. Here are some quick examples of possible uses and common pieces of functionality that you will find useful.</p><h3>Cancel the progressive web app installation if the native app is installed</h3><p>You can intercept the <a href="https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/">beforeinstallprompt</a> event. The general flow is to call preventDefault() on the event so the banner doesn&#39;t show right away, check to see if there are no apps installed and if so call prompt() to show the banner.</p><pre>window.addEventListener(&quot;beforeinstallprompt&quot;, e =&gt; {<br>  if (navigator.getInstalledRelatedApps) {<br>    e.preventDefault();  // Stop automated install prompt.<br>    navigator.getInstalledRelatedApps().then(relatedApps =&gt; {<br>      if (relatedApps.length == 0) {<br>        e.prompt();<br>      }<br>    });<br>  }<br>});</pre><h3>Prevent duplicate notifications</h3><p>One of the intended uses for this API is to allow you to de-dupe notifications from both your Native Application and your Web App. There are a couple of options available to you.</p><p>If the user has your application installed and they don’t have notifications enabled already on your site, you can use the getInstalledRelatedApps() method to selectively disable the UI that the user would normally use to enable the feature.</p><pre>window.addEventListener(&quot;load&quot;, e =&gt; {<br>  if (navigator.getInstalledRelatedApps) {<br>    navigator.getInstalledRelatedApps()<br>    .then(apps =&gt; {<br>      if(apps.length &gt; 0) { /* Hide the UI */ }<br>    });<br>  }<br>});</pre><p>Alternatively, if the user has already been to your site and has Web Push enabled you can unregister the push subscription during the onload event.</p><pre>window.addEventListener(&quot;load&quot;, e =&gt; {<br>  if (navigator.getInstalledRelatedApps) {<br>    let sw = navigator.serviceWorker.ready;<br><br>    navigator.getInstalledRelatedApps()<br>    .then(apps =&gt; (apps.length &gt; 0) ? sw.then(reg =&gt; reg.pushManager) : undefined)<br>    .then(pushManager =&gt; {<br>      if(pushManager) pushManager.unsubscribe();<br>    });<br>  }<br>});</pre><p>This API is not available directly to the service worker so it is not possible to de-duplicate notifications as they arrive at your service worker.</p><h3>Detect if your PWA is installed from a native app</h3><p>If the user has installed your Progressive Web App through the old Add to Homescreen method (i.e, in anything prior to Chrome 58) then it is not possible to detect if your app is installed. Chrome added your site to the Homescreen as a bookmark, and this data was not exposed to the system.</p><p>If the user has installed the web app using the new Web APK functionality, it is possible to determine if your web app is installed. If you know the package name of your Web APK then you can use the context.getPackageManager().getApplicationInfo() API to determine if it is installed. Please note that this is experiemental.</p><h3>Not Working?</h3><p>File a bug <a href="https://bugs.chromium.org/p/chromium/issues/entry?components=Blink&amp;blocking=587623&amp;cc=mgiuca%40chromium.org,owencm%40chromium.org">right here against the Chrome implementation</a>. The correct people will be notified (I’ve sneakily put this in, so I am sure they will be grateful).</p><p>We are keen to keep getting <a href="https://github.com/WICG/get-installed-related-apps/">feedback on the spec</a> and if you have any issues or suggestions, <a href="https://github.com/WICG/get-installed-related-apps/issues">file an issue against the spec</a></p><p><em>Originally published at </em><a href="https://developers.google.com/web/updates/2017/04/getinstalledrelatedapps"><em>developers.google.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2e690b7cb6fb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/dev-channel/detect-if-your-native-app-is-installed-from-your-web-site-2e690b7cb6fb">Detect if your Native app is installed from your web site</a> was originally published in <a href="https://medium.com/dev-channel">Dev Channel</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>