Files
eepp/bin/unit_tests/assets/html/body_height_miscalculation.html
2026-05-23 00:25:27 -03:00

467 lines
38 KiB
HTML

<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><link id="giscus-css" rel="stylesheet" href="body_height_miscalculation/default.css">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>What if your browser built the UI for you? — jonno.nz</title>
<meta name="description" content="We're still shipping hand-crafted frontends while AI can generate entire interfaces. What if the browser itself generated the UI from an API manifest and your preferences?">
<meta name="author" content="John Gregoriadis">
<meta name="theme-color" content="#0c1520">
<meta name="color-scheme" content="dark">
<link rel="canonical" href="https://jonno.nz/posts/what-if-your-browser-built-the-ui-for-you/">
<link rel="icon" href="https://jonno.nz/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="body_height_miscalculation.css">
<link rel="alternate" type="application/rss+xml" title="jonno.nz" href="https://jonno.nz/feed.xml">
<link rel="alternate" type="application/feed+json" title="jonno.nz" href="https://jonno.nz/feed.json">
<!-- Open Graph -->
<meta property="og:site_name" content="jonno.nz">
<meta property="og:locale" content="en_NZ">
<meta property="og:type" content="article">
<meta property="og:title" content="What if your browser built the UI for you? — jonno.nz">
<meta property="og:description" content="We're still shipping hand-crafted frontends while AI can generate entire interfaces. What if the browser itself generated the UI from an API manifest and your preferences?">
<meta property="og:url" content="https://jonno.nz/posts/what-if-your-browser-built-the-ui-for-you/">
<meta property="og:image" content="https://jonno.nz/og/what-if-your-browser-built-the-ui-for-you.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="What if your browser built the UI for you?">
<meta property="article:published_time" content="2026-04-05">
<meta property="article:author" content="John Gregoriadis">
<meta property="article:tag" content="ai">
<meta property="article:tag" content="architecture">
<meta property="article:tag" content="engineering">
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1">
<meta property="og:type" content="article">
<meta property="og:site_name" content="jonno.nz">
<meta property="og:title" content="What if your browser built the UI for you?">
<meta property="og:description" content="We're still shipping hand-crafted frontends while AI can generate entire interfaces. What if the browser itself generated the UI from an API manifest and…">
<meta property="og:url" content="https://jonno.nz/posts/what-if-your-browser-built-the-ui-for-you/">
<meta property="og:image" content="https://jonno.nz/og/what-if-your-browser-built-the-ui-for-you.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="description" content="We're still shipping hand-crafted frontends while AI can generate entire interfaces. What if the browser itself generated the UI from an API manifest and…">
<meta name="theme-color" content="#0c1520">
<meta name="generator" content="Lume v2.4.2">
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<nav class="site-nav" aria-label="Primary">
<div class="wrap">
<a href="https://jonno.nz/" class="brand" aria-label="jonno.nz — home">jonno.nz</a>
<div class="links">
<a href="https://jonno.nz/">writing</a>
<a href="https://jonno.nz/projects/">projects</a>
<a href="https://jonno.nz/about/">about</a>
<a href="https://jonno.nz/tags/">tags</a>
<a href="https://jonno.nz/feed.xml" aria-label="RSS feed">rss</a>
</div>
</div>
</nav>
<div class="progress-top" aria-hidden="true"><div class="bar" id="progress-bar" style="width: 0%;"></div></div>
<main id="main" class="shell post-shell" role="main">
<article id="top">
<div class="header-box" aria-label="Post metadata">
<div class="hb-row">
<span class="hb-k">published</span>
<span class="hb-v">
<time datetime="2026-04-05">2026-04-05</time>
</span>
</div>
<div class="hb-row">
<span class="hb-k">reading</span>
<span class="hb-v">5 min · 1,132 words</span>
</div>
<div class="hb-row">
<span class="hb-k">tags</span>
<span class="hb-v">
<a href="https://jonno.nz/tags/ai/" class="tag">ai</a><a href="https://jonno.nz/tags/architecture/" class="tag">architecture</a><a href="https://jonno.nz/tags/engineering/" class="tag">engineering</a>
</span>
</div>
</div>
<header class="post-hero">
<div class="hero-text">
<h1>What if your browser built the UI for you?</h1>
<p class="dek">We're still shipping hand-crafted frontends while
AI can generate entire interfaces. What if the browser itself generated
the UI from an API manifest and your preferences?</p>
<div class="byline">
<span>By <b><a href="https://jonno.nz/about/" rel="author">John Gregoriadis</a></b></span>
<span>5 April 2026</span>
<span>5 min read</span>
</div>
</div>
<div class="mm-art" data-seed="What if your browser built the UI for you?" aria-hidden="true"><svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g class="art-spin" style="--dur:23s;--dir:normal"><path d="M57.8,79.6L24.3,66.5L26.3,30.6L61.1,21.5L80.5,51.8Z" fill="none" stroke="#3d7b8a" stroke-width="0.9" opacity="0.5" pathLength="1" class="art-draw" style="--d:0.00s"></path><line x1="57.8" y1="79.6" x2="61.1" y2="21.5" stroke="#3d7b8a" stroke-width="0.5" opacity="0.3" pathLength="1" class="art-draw" style="--d:0.20s"></line><line x1="24.3" y1="66.5" x2="80.5" y2="51.8" stroke="#3d7b8a" stroke-width="0.5" opacity="0.3" pathLength="1" class="art-draw" style="--d:0.27s"></line><line x1="26.3" y1="30.6" x2="57.8" y2="79.6" stroke="#3d7b8a" stroke-width="0.5" opacity="0.2" pathLength="1" class="art-draw" style="--d:0.34s"></line><line x1="61.1" y1="21.5" x2="24.3" y2="66.5" stroke="#3d7b8a" stroke-width="0.5" opacity="0.3" pathLength="1" class="art-draw" style="--d:0.41s"></line><line x1="80.5" y1="51.8" x2="26.3" y2="30.6" stroke="#3d7b8a" stroke-width="0.5" opacity="0.3" pathLength="1" class="art-draw" style="--d:0.48s"></line><line x1="50.0" y1="50.0" x2="57.8" y2="79.6" stroke="#3d7b8a" stroke-width="0.3" opacity="0.2" pathLength="1" class="art-draw" style="--d:0.70s"></line><line x1="50.0" y1="50.0" x2="24.3" y2="66.5" stroke="#3d7b8a" stroke-width="0.3" opacity="0.1" pathLength="1" class="art-draw" style="--d:0.74s"></line><line x1="50.0" y1="50.0" x2="26.3" y2="30.6" stroke="#3d7b8a" stroke-width="0.3" opacity="0.1" pathLength="1" class="art-draw" style="--d:0.78s"></line><line x1="50.0" y1="50.0" x2="61.1" y2="21.5" stroke="#3d7b8a" stroke-width="0.3" opacity="0.1" pathLength="1" class="art-draw" style="--d:0.82s"></line><line x1="50.0" y1="50.0" x2="80.5" y2="51.8" stroke="#3d7b8a" stroke-width="0.3" opacity="0.1" pathLength="1" class="art-draw" style="--d:0.86s"></line><circle cx="57.8" cy="79.6" r="1.2" fill="#3d7b8a" class="art-dot" style="--d:0.90s;--op:0.6"></circle><circle cx="24.3" cy="66.5" r="1.2" fill="#3d7b8a" class="art-pulse-dot" style="--d:0.95s;--op:0.8;--pd:3.7s"></circle><circle cx="26.3" cy="30.6" r="1.2" fill="#3d7b8a" class="art-dot" style="--d:1.00s;--op:0.8"></circle><circle cx="61.1" cy="21.5" r="1.2" fill="#3d7b8a" class="art-pulse-dot" style="--d:1.05s;--op:0.5;--pd:2.8s"></circle><circle cx="80.5" cy="51.8" r="1.2" fill="#3d7b8a" class="art-dot" style="--d:1.10s;--op:0.7"></circle><path d="M33.6,68.5L25.8,55.3L27.3,40.1L37.5,28.7L52.4,25.4L66.4,31.5L74.2,44.7L72.7,59.9L62.5,71.3L47.6,74.6Z" fill="none" stroke="#3d7b8a" stroke-width="0.3" opacity="0.1" pathLength="1" class="art-draw" style="--d:1.59s"></path><path d="M73.9,50.2 A23.9,23.9 0 0 1 37.9,70.6" fill="none" stroke="#3d7b8a" stroke-width="0.6" opacity="0.4" pathLength="1" class="art-draw" style="--d:1.71s"></path></g><g class="art-spin" style="--dur:38s;--dir:reverse"><path d="M47.3,63.9L35.9,51.8L44.0,37.2L60.4,40.3L62.4,56.8Z" fill="none" stroke="#3d7b8a" stroke-width="0.9" opacity="0.4" pathLength="1" class="art-draw" style="--d:0.55s"></path><circle cx="47.3" cy="63.9" r="0.9" fill="#3d7b8a" class="art-pulse-dot" style="--d:1.15s;--op:0.5;--pd:4.1s"></circle><circle cx="35.9" cy="51.8" r="0.9" fill="#3d7b8a" class="art-pulse-dot" style="--d:1.19s;--op:0.5;--pd:4.1s"></circle><circle cx="44.0" cy="37.2" r="0.9" fill="#3d7b8a" class="art-pulse-dot" style="--d:1.23s;--op:0.5;--pd:4.7s"></circle><circle cx="60.4" cy="40.3" r="0.9" fill="#3d7b8a" class="art-dot" style="--d:1.27s;--op:0.5"></circle><circle cx="62.4" cy="56.8" r="0.9" fill="#3d7b8a" class="art-pulse-dot" style="--d:1.31s;--op:0.4;--pd:4.0s"></circle><circle cx="50" cy="50" r="2.4" fill="none" stroke="#3d7b8a" stroke-width="0.7" opacity="0.4" pathLength="1" class="art-draw" style="--d:1.51s"></circle></g><g class="art-orbit" style="--dur:6.1s;--dir:reverse"><circle cx="70.0" cy="50.0" r="1.2" fill="#3d7b8a" class="art-dot" style="--d:1.35s;--op:0.4"></circle></g><g class="art-orbit" style="--dur:4.7s;--dir:normal"><circle cx="75.4" cy="50.0" r="1.2" fill="#3d7b8a" class="art-dot" style="--d:1.43s;--op:0.6"></circle></g></svg></div>
</header>
<div class="post-content">
<p><span class="first-word">We're</span> at a genuinely weird inflection point in frontend development. AI can
generate entire interfaces now. LLMs can reason about data and layout. And yet —
most SaaS products still ship hand-crafted React apps, each building its own UI,
its own accessibility layer, its own theme system, its own responsive
breakpoints. Not every service, but the vast majority.</p>
<p>That's a lot of duplicated effort for what's essentially the same job — showing
a human some data and letting them do stuff with it.</p>
<p>I've been thinking about this a lot lately, and I built a proof of concept to
test an idea: what if the browser itself generated the UI?</p>
<h2>Where we are right now</h2>
<p>The industry is circling this idea from multiple angles, but nobody's quite
landed on it yet.</p>
<p><a href="https://www.apollographql.com/docs/graphos/schema-design/guides/sdui/basics">Server-driven UI</a>
has been around for a while — Airbnb and others pioneered it for mobile, where
app store review cycles make shipping UI changes painful. The server sends down
a JSON tree describing what to render, and the client just follows instructions.
It's clever, but the server is still calling the shots. x.</p>
<p>Google recently shipped
<a href="https://developers.google.com/natively-adaptive-interfaces">Natively Adaptive Interfaces</a>
— a framework that uses AI agents to make accessibility a default rather than an
afterthought. Really cool idea, and the right instinct. But it's still operating
within a single app's boundaries. Your accessibility preferences don't carry
between Google's products and, say, your project management tool.</p>
<p>Then there's the
<a href="https://www.copilotkit.ai/blog/the-developer-s-guide-to-generative-ui-in-2026">generative UI</a>
wave — CopilotKit, Vercel's AI SDK, and others building frameworks where LLMs
generate components on the fly. These are powerful developer tools, but they're
still developer tools. The generation happens at build time or on the server.
The service is still in control.</p>
<p>See the pattern? Every approach keeps the power on the service side.</p>
<h2>Flip it</h2>
<p>Here's the idea behind the
<a href="https://github.com/jonnonz1/adaptive-browser">adaptive browser</a>: what if the
generation happened on <em>your</em> side?</p>
<p>Instead of a service shipping you a finished frontend, it publishes a manifest —
a structured description of what it can do. Its capabilities, endpoints, data
shapes, what actions are available. Think of it like an API spec, but semantic.
Not just "here's a GET endpoint" but "here's a list of repositories, they're
sortable by stars and language, you can create, delete, star, or fork them."</p>
<p>Your browser takes that manifest, calls the actual APIs, gets real data back,
and then generates the UI based on your preferences. Your font size. Your colour
scheme. Your preferred layout (tables vs cards vs kanban). Your accessibility
needs. All applied universally, across every service.</p>
<p>The manifest for something like GitHub looks roughly like this — a service
describes its capabilities and the browser figures out the rest:</p>
<pre><code class="language-yaml hljs"><span class="hljs-attr">service:</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">"GitHub"</span>
<span class="hljs-attr">domain:</span> <span class="hljs-string">"api.github.com"</span>
<span class="hljs-attr">capabilities:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">"repositories"</span>
<span class="hljs-attr">endpoints:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">"/user/repos"</span>
<span class="hljs-attr">semantic:</span> <span class="hljs-string">"list"</span>
<span class="hljs-attr">entity:</span> <span class="hljs-string">"repository"</span>
<span class="hljs-attr">sortable_fields:</span> [<span class="hljs-string">name</span>, <span class="hljs-string">updated_at</span>, <span class="hljs-string">stargazers_count</span>]
<span class="hljs-attr">actions:</span> [<span class="hljs-string">create</span>, <span class="hljs-string">delete</span>, <span class="hljs-string">star</span>, <span class="hljs-string">fork</span>]
</code></pre>
<p>The browser takes that, fetches the data, and generates a bespoke interface —
using an LLM to reason about the best way to present it given who you are and
what you're trying to do.</p>
<h2>Why this matters more than it sounds</h2>
<p>When I was building the app store and integrations platforms at Xero, one of the
constant headaches was that every third-party integration had its own UI
patterns. Users had to learn a new interface for every app they connected. If
the browser was generating the UI from a shared set of preferences, that problem
just… goes away.</p>
<p>Accessibility is the big one though. Right now, accessibility is a feature that
gets bolted on — and often badly. When the browser generates the UI,
accessibility isn't a feature. It's the default. Your preferences — high
contrast, keyboard-first navigation, screen reader optimisation, larger text —
apply everywhere. Not because every developer remembered to implement them, but
because they're baked into how the UI gets generated in the first place.</p>
<p>Customisation becomes genuinely personal too. Not "pick from three themes the
developer made" but "this is how I interact with software, full stop."</p>
<h2>The trade-off is real though</h2>
<p>Frontend complexity drops dramatically, but the complexity doesn't disappear —
it moves behind the API. And honestly, it probably increases.</p>
<p>API design becomes way more important. You can't just throw together some REST
endpoints and call it a day. Your manifest needs to be semantic — describing
what the data means, not just what shape it is. Data contracts between services
matter more. Versioning matters more.</p>
<div class="mermaid" data-processed="true"><svg id="mermaid-1779423215521" width="100%" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 859.25px;" viewBox="0 0 859.25 278" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#mermaid-1779423215521{font-
family:"trebuchet
ms",verdana,arial,sans-serif;font-size:16px;fill:#ccc;}@keyframes
edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes
dash{to{stroke-dashoffset:0;}}#mermaid-1779423215521
.edge-animation-slow{stroke-dasharray:9,5!important;stroke-
dashoffset:900;animation:dash 50s linear
infinite;stroke-linecap:round;}#mermaid-1779423215521
.edge-animation-fast{stroke-dasharray:9,5!important;stroke-
dashoffset:900;animation:dash 20s linear
infinite;stroke-linecap:round;}#mermaid-1779423215521
.error-icon{fill:#a44141;}#mermaid-1779423215521
.error-text{fill:#ddd;stroke:#ddd;}#mermaid-1779423215521
.edge-thickness-normal{stroke-width:1px;}#mermaid-1779423215521
.edge-thickness-thick{stroke-width:3.5px;}#mermaid-1779423215521
.edge-pattern-solid{stroke-dasharray:0;}#mermaid-1779423215521
.edge-thickness-invisible{stroke-width:0;fill:none;}
#mermaid-1779423215521
.edge-pattern-dashed{stroke-dasharray:3;}#mermaid-1779423215521
.edge-pattern-dotted{stroke-dasharray:2;}#mermaid-1779423215521
.marker{fill:lightgrey;stroke:lightgrey;}#mermaid-1779423215521
.marker.cross{stroke:lightgrey;}#mermaid-1779423215521
svg{font-family:"trebuchet
ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-1779423215521
p{margin:0;}#mermaid-1779423215521 .label{font-family:"trebuchet
ms",verdana,arial,sans-serif;color:#ccc;}#mermaid-1779423215521
.cluster-label text{fill:#F9FFFE;}#mermaid-1779423215521 .cluster-label
span{color:#F9FFFE;}#mermaid-1779423215521 .cluster-label span
p{background-color:transparent;}#mermaid-1779423215521 .label
text,#mermaid-1779423215521
span{fill:#ccc;color:#ccc;}#mermaid-1779423215521 .node
rect,#mermaid-1779423215521 .node circle,#mermaid-1779423215521 .node
ellipse,#mermaid-1779423215521 .node polygon,#mermaid-1779423215521
.node
path{fill:#1f2020;stroke:#ccc;stroke-width:1px;}#mermaid-1779423215521
.rough-node .label text,#mermaid-1779423215521 .node .label
text,#mermaid-1779423215521 .image-shape .label,#mermaid-1779423215521
.icon-shape .label{text-anchor:middle;}#mermaid-1779423215521 .node
.katex
path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-1779423215521
.rough-node .label,#mermaid-1779423215521 .node
.label,#mermaid-1779423215521 .image-shape .label,#mermaid-1779423215521
.icon-shape .label{text-align:center;}#mermaid-1779423215521
.node.clickable{cursor:pointer;}#mermaid-1779423215521 .root .anchor
path{fill:lightgrey!important;stroke-width:0;stroke:lightgrey;}
#mermaid-1779423215521
.arrowheadPath{fill:lightgrey;}#mermaid-1779423215521 .edgePath
.path{stroke:lightgrey;stroke-width:1px;}#mermaid-1779423215521
.flowchart-link{stroke:lightgrey;fill:none;}#mermaid-1779423215521
.edgeLabel{background-color:hsl(0, 0%,
34.4117647059%);text-align:center;}#mermaid-1779423215521 .edgeLabel
p{background-color:hsl(0, 0%, 34.4117647059%);}#mermaid-1779423215521
.edgeLabel rect{opacity:0.5;background-color:hsl(0, 0%,
34.4117647059%);fill:hsl(0, 0%, 34.4117647059%);}#mermaid-1779423215521
.labelBkg{background-color:rgba(87.75, 87.75, 87.75,
0.5);}#mermaid-1779423215521 .cluster rect{fill:hsl(180, 1.5873015873%,
28.3529411765%);stroke:rgba(255, 255, 255,
0.25);stroke-width:1px;}#mermaid-1779423215521 .cluster
text{fill:#F9FFFE;}#mermaid-1779423215521 .cluster
span{color:#F9FFFE;}#mermaid-1779423215521
div.mermaidTooltip{position:absolute;text-align:center;max-
width:200px;padding:2px;font-family:"trebuchet
ms",verdana,arial,sans-serif;font-size:12px;background:hsl(20,
1.5873015873%, 12.3529411765%);border:1px solid rgba(255, 255, 255,
0.25);border-radius:2px;pointer-events:none;z-index:100;}
#mermaid-1779423215521
.flowchartTitleText{text-anchor:middle;font-size:18px;fill:#ccc;}
#mermaid-1779423215521
rect.text{fill:none;stroke-width:0;}#mermaid-1779423215521
.icon-shape,#mermaid-1779423215521 .image-shape{background-color:hsl(0,
0%, 34.4117647059%);text-align:center;}#mermaid-1779423215521
.icon-shape p,#mermaid-1779423215521 .image-shape
p{background-color:hsl(0, 0%,
34.4117647059%);padding:2px;}#mermaid-1779423215521 .icon-shape .label
rect,#mermaid-1779423215521 .image-shape .label
rect{opacity:0.5;background-color:hsl(0, 0%, 34.4117647059%);fill:hsl(0,
0%, 34.4117647059%);}#mermaid-1779423215521
.label-icon{display:inline-block;height:1em;overflow:visible;vertical-
align:-0.125em;}#mermaid-1779423215521 .node .label-icon
path{fill:currentColor;stroke:revert;stroke-width:revert;}
#mermaid-1779423215521 .node
.neo-node{stroke:#ccc;}#mermaid-1779423215521 [data-look="neo"].node
rect,#mermaid-1779423215521 [data-look="neo"].cluster
rect,#mermaid-1779423215521 [data-look="neo"].node
polygon{stroke:url(#mermaid-1779423215521-gradient);filter:drop-shadow(
1px 2px 2px rgba(185,185,185,1));}#mermaid-1779423215521
[data-look="neo"].node
path{stroke:url(#mermaid-1779423215521-gradient);stroke-width:1px;}
#mermaid-1779423215521 [data-look="neo"].node
.outer-path{filter:drop-shadow( 1px 2px 2px
rgba(185,185,185,1));}#mermaid-1779423215521 [data-look="neo"].node
.neo-line path{stroke:#ccc;filter:none;}#mermaid-1779423215521
[data-look="neo"].node
circle{stroke:url(#mermaid-1779423215521-gradient);filter:drop-shadow(
1px 2px 2px rgba(185,185,185,1));}#mermaid-1779423215521
[data-look="neo"].node circle
.state-start{fill:#000000;}#mermaid-1779423215521
[data-look="neo"].icon-shape
.icon{fill:url(#mermaid-1779423215521-gradient);filter:drop-shadow( 1px
2px 2px rgba(185,185,185,1));}#mermaid-1779423215521
[data-look="neo"].icon-shape .icon-neo
path{stroke:url(#mermaid-1779423215521-gradient);filter:drop-shadow( 1px
2px 2px rgba(185,185,185,1));}#mermaid-1779423215521
:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker id="mermaid-1779423215521_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker><marker id="mermaid-1779423215521_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker><marker id="mermaid-1779423215521_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" class="arrowMarkerPath" style="stroke-width: 0px; stroke-dasharray: 1px, 0px;"></path></marker><marker id="mermaid-1779423215521_flowchart-v2-pointStart-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" class="arrowMarkerPath" style="stroke-width: 0px; stroke-dasharray: 1px, 0px;"></polygon></marker><marker id="mermaid-1779423215521_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></circle></marker><marker id="mermaid-1779423215521_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></circle></marker><marker id="mermaid-1779423215521_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0px; stroke-dasharray: 1px, 0px;"></circle></marker><marker id="mermaid-1779423215521_flowchart-v2-circleStart-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0px; stroke-dasharray: 1px, 0px;"></circle></marker><marker id="mermaid-1779423215521_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2px; stroke-dasharray: 1px, 0px;"></path></marker><marker id="mermaid-1779423215521_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2px; stroke-dasharray: 1px, 0px;"></path></marker><marker id="mermaid-1779423215521_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5px;"></path></marker><marker id="mermaid-1779423215521_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5px; stroke-dasharray: 1px, 0px;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M156.875,35L181.917,35C206.958,35,257.042,35,305.772,47.522C354.503,60.044,401.881,85.087,425.57,97.609L449.259,110.131" id="mermaid-1779423215521-L_A_B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_A_B_0" data-points="W3sieCI6MTU2Ljg3NSwieSI6MzV9LHsieCI6MzA3LjEyNSwieSI6MzV9LHsieCI6NDUyLjc5NTY3MzA3NjkyMzEsInkiOjExMn1d" data-look="classic" marker-end="url(#mermaid-1779423215521_flowchart-v2-pointEnd)"></path><path d="M193.25,139L212.229,139C231.208,139,269.167,139,306.458,139C343.75,139,380.375,139,398.688,139L417,139" id="mermaid-1779423215521-L_C_B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_C_B_0" data-points="W3sieCI6MTkzLjI1LCJ5IjoxMzl9LHsieCI6MzA3LjEyNSwieSI6MTM5fSx7IngiOjQyMSwieSI6MTM5fV0=" data-look="classic" marker-end="url(#mermaid-1779423215521_flowchart-v2-pointEnd)"></path><path d="M183.125,243L203.792,243C224.458,243,265.792,243,310.147,230.478C354.503,217.956,401.881,192.913,425.57,180.391L449.259,167.869" id="mermaid-1779423215521-L_D_B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_D_B_0" data-points="W3sieCI6MTgzLjEyNSwieSI6MjQzfSx7IngiOjMwNy4xMjUsInkiOjI0M30seyJ4Ijo0NTIuNzk1NjczMDc2OTIzMSwieSI6MTY2fV0=" data-look="classic" marker-end="url(#mermaid-1779423215521_flowchart-v2-pointEnd)"></path><path d="M586.75,139L597.104,139C607.458,139,628.167,139,648.208,139C668.25,139,687.625,139,697.313,139L707,139" id="mermaid-1779423215521-L_B_E_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_B_E_0" data-points="W3sieCI6NTg2Ljc1LCJ5IjoxMzl9LHsieCI6NjQ4Ljg3NSwieSI6MTM5fSx7IngiOjcxMSwieSI6MTM5fV0=" data-look="classic" marker-end="url(#mermaid-1779423215521_flowchart-v2-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(307.125, 35)"><g class="label" data-id="L_A_B_0" transform="translate(-88.875, -12)"><foreignObject width="177.75" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml" class="labelBkg"><span class="edgeLabel"><p>Publishes manifest + APIs</p></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_C_B_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml" class="labelBkg"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_D_B_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml" class="labelBkg"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(648.875, 139)"><g class="label" data-id="L_B_E_0" transform="translate(-37.125, -12)"><foreignObject width="74.25" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml" class="labelBkg"><span class="edgeLabel"><p>Generates</p></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="mermaid-1779423215521-flowchart-A-0" data-look="classic" transform="translate(100.625, 35)"><rect class="basic label-container" style="" x="-56.25" y="-27" width="112.5" height="54"></rect><g class="label" style="" transform="translate(-26.25, -12)"><rect></rect><foreignObject width="52.5" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Service</p></span></div></foreignObject></g></g><g class="node default" id="mermaid-1779423215521-flowchart-B-1" data-look="classic" transform="translate(503.875, 139)"><rect class="basic label-container" style="" x="-82.875" y="-27" width="165.75" height="54"></rect><g class="label" style="" transform="translate(-52.875, -12)"><rect></rect><foreignObject width="105.75" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Browser Agent</p></span></div></foreignObject></g></g><g class="node default" id="mermaid-1779423215521-flowchart-C-2" data-look="classic" transform="translate(100.625, 139)"><rect class="basic label-container" style="" x="-92.625" y="-27" width="185.25" height="54"></rect><g class="label" style="" transform="translate(-62.625, -12)"><rect></rect><foreignObject width="125.25" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>User Preferences</p></span></div></foreignObject></g></g><g class="node default" id="mermaid-1779423215521-flowchart-D-4" data-look="classic" transform="translate(100.625, 243)"><rect class="basic label-container" style="" x="-82.5" y="-27" width="165" height="54"></rect><g class="label" style="" transform="translate(-52.5, -12)"><rect></rect><foreignObject width="105" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Org Guardrails</p></span></div></foreignObject></g></g><g class="node default" id="mermaid-1779423215521-flowchart-E-7" data-look="classic" transform="translate(781.125, 139)"><rect class="basic label-container" style="" x="-70.125" y="-27" width="140.25" height="54"></rect><g class="label" style="" transform="translate(-40.125, -12)"><rect></rect><foreignObject width="80.25" height="24"><div style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Bespoke UI</p></span></div></foreignObject></g></g></g></g></g><defs><filter id="mermaid-1779423215521-drop-shadow" height="130%" width="130%"><feDropShadow dx="4" dy="4" stdDeviation="0" flood-opacity="0.06" flood-color="#FFFFFF"></feDropShadow></filter></defs><defs><filter id="mermaid-1779423215521-drop-shadow-small" height="150%" width="150%"><feDropShadow dx="2" dy="2" stdDeviation="0" flood-opacity="0.06" flood-color="#FFFFFF"></feDropShadow></filter></defs><linearGradient id="mermaid-1779423215521-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#cccccc" stop-opacity="1"></stop><stop offset="100%" stop-color="hsl(180, 0%, 18.3529411765%)" stop-opacity="1"></stop></linearGradient></svg></div>
<p>But here's the thing — this trade-off pushes us somewhere genuinely interesting.
If every service needs to describe itself semantically through APIs and
manifests, those APIs become the actual product surface. Not the frontend. The
APIs.</p>
<p>And once APIs are the product surface, sharing context between platforms becomes
the interesting problem. Your project management tool knows what you're working
on. Your email client knows who you're talking to. Your code editor knows what
you're building. Right now, none of these talk to each other in any meaningful
way because they're all locked behind their own UIs. In a manifest-driven world,
that context flows through the APIs — and your browser can stitch it all
together into something coherent.</p>
<h2>Where this is headed (IMHO)</h2>
<p>I reckon we're about 3-5 years from this being mainstream. The pieces are all
there — LLMs that can reason about UI,
<a href="https://www.builder.io/blog/ui-over-apis">standardisation efforts</a> around
sending UI intent over APIs, and a growing expectation from users that software
should adapt to them, not the other way around.</p>
<p>The services that win in this world won't be the ones with the prettiest
hand-crafted UI. They'll be the ones with the best APIs, the richest manifests,
and the most useful data. The frontend becomes a generated output, not a
hand-crafted input.</p>
<p>Organisations will set preference guardrails — "our people can use dark or light
mode, must have destructive action confirmations, these fields are always
visible" — while individuals customise within those bounds. Your browser becomes
your agent, not just a renderer.</p>
<p>I built the <a href="https://github.com/jonnonz1/adaptive-browser">adaptive browser</a> as
a proof of concept to test this thinking — it uses Claude to generate UIs from a
GitHub manifest and user preferences defined in YAML. It's rough, but the
direction feels right.</p>
<p>The frontend isn't dying. But what we think of as "frontend development" is
about to change. The interesting work moves to API design, semantic data
contracts, and building browsers smart enough to be genuine user agents.</p>
</div>
<footer class="post-footer">
<div class="footer-title">Continue reading</div>
<nav class="post-nav" aria-label="Post navigation">
<a href="https://jonno.nz/posts/stealing-nanoclaw-patterns-for-webapps-and-saas/" class="cell" rel="prev">
<div class="dir">← Previous</div>
<div class="ttl">Stealing NanoClaw Patterns for Web Apps and SaaS</div>
</a>
<a href="https://jonno.nz/posts/claude-code-running-claude-code-in-4-second-disposable-vms/" class="cell right" rel="next">
<div class="dir">Next →</div>
<div class="ttl">Claude Code Running Claude Code in 4-Second Disposable VMs</div>
</a>
</nav>
<section class="related" aria-labelledby="related-heading">
<div class="footer-title" id="related-heading">Related</div>
<ul class="related-list">
<li>
<a href="https://jonno.nz/posts/conscious-minimalism/">
<span class="rl-date">14 · 05 · 2026</span>
<span class="rl-title">Conscious Minimalism</span>
</a>
</li>
<li>
<a href="https://jonno.nz/posts/open-source-agent-that-teaches-claude-code-your-architecture/">
<span class="rl-date">15 · 04 · 2026</span>
<span class="rl-title">Open-Source Agent That Teaches Claude Code Your Architecture</span>
</a>
</li>
<li>
<a href="https://jonno.nz/posts/claude-code-can-now-spawn-copies-of-itself-in-isolated-vms/">
<span class="rl-date">13 · 04 · 2026</span>
<span class="rl-title">Claude Code Can Now Spawn Copies of Itself in Isolated VMs</span>
</a>
</li>
</ul>
</section>
<aside class="newsletter" id="newsletter" aria-label="Newsletter signup">
<div class="nl-text">
<h2 class="nl-title">Get new posts <em>in your inbox</em>.</h2>
<p class="nl-desc">No ads, no tracking, no AI-generated filler. One email when something's worth reading.</p>
</div>
<div class="nl-form-wrap">
<form class="nl-form" novalidate="">
<label class="sr-only" for="nl-email">Email address</label>
<input id="nl-email" type="email" name="email" placeholder="you@example.com" required="" autocomplete="email" data-_extension-text-contrast="">
<button type="submit" data-_extension-text-contrast="">
<span class="nl-btn-text">Subscribe</span>
<svg class="nl-spinner" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5" stroke-dasharray="28" stroke-dashoffset="8" stroke-linecap="round"></circle>
</svg>
</button>
</form>
<p class="nl-success" role="status">You're in — I'll let you know when something new drops.</p>
<p class="nl-error" role="alert">Something went wrong. Try again?</p>
<div class="nl-count"><span>Readers</span><b>1387 subscribers</b></div>
</div>
</aside>
<section class="comments" aria-labelledby="comments-heading">
<h2 id="comments-heading">Comments</h2>
<div class="giscus"></div>
</section>
</footer>
</article>
</main>
<footer class="site-footer">
<div class="wrap">
<span>John Gregoriadis · Auckland, NZ</span>
<span>© 2026 · <a href="https://jonno.nz/feed.xml">rss</a> · <a href="https://jonno.nz/feed.json">json</a> · <a href="https://jonno.nz/about/">about</a></span>
</div>
</footer>
<div class="mermaidTooltip" style="opacity: 0; position: absolute; text-align: center; max-width: 200px; padding: 2px; font-size: 12px; background: rgb(255, 255, 222); border: 1px solid rgb(51, 51, 51); border-radius: 2px; pointer-events: none; z-index: 100;"></div></body></html>