Skip to content

Commit d51a06d

Browse files
authored
blog: publish dates, harness diagram, and header theme toggle (#1613)
1 parent f1fe57d commit d51a06d

8 files changed

Lines changed: 132 additions & 3 deletions

File tree

90.4 KB
Loading
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
// Same behavior as website/index.html theme toggle: persists `iii_theme`,
3+
// swaps CSS variables + icon, updates theme-color meta, dispatches iii:theme-change.
4+
---
5+
6+
<button type="button" id="theme-btn" class="theme-btn" aria-label="Toggle theme">
7+
<svg id="theme-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
8+
<!-- moon when current theme is light (click → dark) -->
9+
<path d="M12.5 9.5 A 5 5 0 1 1 6.5 3.5 A 4 4 0 0 0 12.5 9.5 Z"></path>
10+
</svg>
11+
</button>
12+
13+
<script is:inline>
14+
(function () {
15+
var themeBtn = document.getElementById('theme-btn')
16+
var themeIcon = document.getElementById('theme-icon')
17+
if (!themeBtn || !themeIcon) return
18+
var THEME_KEY = 'iii_theme'
19+
var isDark = (window.__iiiThemeInit && window.__iiiThemeInit.dark) || false
20+
var THEME_ACCENT = { light: '#ff5a1f', dark: '#3ea8ff' }
21+
function setAria() {
22+
themeBtn.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode')
23+
}
24+
function syncIcon() {
25+
if (isDark) {
26+
themeIcon.innerHTML =
27+
'<circle cx="8" cy="8" r="3"/><path d="M8 1 L8 3 M8 13 L8 15 M1 8 L3 8 M13 8 L15 8 M3 3 L4.5 4.5 M11.5 11.5 L13 13 M3 13 L4.5 11.5 M11.5 4.5 L13 3"/>'
28+
} else {
29+
themeIcon.innerHTML = '<path d="M12.5 9.5 A 5 5 0 1 1 6.5 3.5 A 4 4 0 0 0 12.5 9.5 Z"/>'
30+
}
31+
}
32+
if (isDark) syncIcon()
33+
setAria()
34+
themeBtn.addEventListener('click', function () {
35+
isDark = !isDark
36+
try {
37+
localStorage.setItem(THEME_KEY, isDark ? 'dark' : 'light')
38+
} catch (e) {}
39+
document.documentElement.dataset.theme = isDark ? 'dark' : 'light'
40+
var r = document.documentElement.style
41+
if (isDark) {
42+
r.setProperty('--bg', '#111110')
43+
r.setProperty('--panel', '#1a1916')
44+
r.setProperty('--paper', '#111110')
45+
r.setProperty('--paper-2', '#222220')
46+
r.setProperty('--ink', '#f2f0ed')
47+
r.setProperty('--ink-2', '#e2dfd9')
48+
r.setProperty('--ink-soft', '#e2dfd9')
49+
r.setProperty('--ink-faint', '#9c9893')
50+
r.setProperty('--ink-ghost', '#5d5a55')
51+
r.setProperty('--mute', '#9c9893')
52+
r.setProperty('--mute-2', '#5d5a55')
53+
r.setProperty('--rule', '#2a2926')
54+
r.setProperty('--rule-2', '#1f1e1c')
55+
r.setProperty('--accent', THEME_ACCENT.dark)
56+
} else {
57+
r.setProperty('--bg', '#f5f3ee')
58+
r.setProperty('--panel', '#e9e6e2')
59+
r.setProperty('--paper', '#f5f3ee')
60+
r.setProperty('--paper-2', '#ebe8e3')
61+
r.setProperty('--ink', '#0a0a0a')
62+
r.setProperty('--ink-2', '#1a1a1a')
63+
r.setProperty('--ink-soft', '#1a1a1a')
64+
r.setProperty('--ink-faint', '#6b6865')
65+
r.setProperty('--ink-ghost', '#a3a09c')
66+
r.setProperty('--mute', '#6b6865')
67+
r.setProperty('--mute-2', '#a3a09c')
68+
r.setProperty('--rule', '#d8d4cb')
69+
r.setProperty('--rule-2', '#e6e3df')
70+
r.setProperty('--accent', THEME_ACCENT.light)
71+
}
72+
syncIcon()
73+
setAria()
74+
var meta = document.querySelector('meta[name="theme-color"]')
75+
if (meta) meta.content = isDark ? '#111110' : '#f5f3ee'
76+
window.dispatchEvent(
77+
new CustomEvent('iii:theme-change', {
78+
detail: {
79+
theme: isDark ? 'dark' : 'light',
80+
accent: isDark ? THEME_ACCENT.dark : THEME_ACCENT.light,
81+
},
82+
}),
83+
)
84+
})
85+
})()
86+
</script>

blog/src/content/blog/add-a-worker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: 'Add a worker'
33
description: 'The cloud is a bazaar, and that was okay until agents arrived and had to contend with 10,000 shops. Three primitives, one engine, and one answer to every question.'
4-
pubDate: 2026-05-05
4+
pubDate: 2026-05-07
55
author: 'Mike Piccolo, Founder & CEO of iii'
66
tags: ['agents', 'architecture', 'workers', 'cloudflare']
77
---

blog/src/content/blog/the-harness-is-the-backend.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
title: 'The Harness Is the Backend'
44
description: 'The agent harness debate takes for granted that the harness is its own world, separate from the backend. iii makes a different bet: the harness is the backend.'
5-
pubDate: 2026-05-06
5+
pubDate: 2026-04-28
66
author: 'Mike Piccolo, Founder & CEO of iii'
77
tags: ['agents', 'architecture', 'harness', 'backend']
88
---
@@ -40,6 +40,8 @@ another: "the backend".
4040
I believe that this is temporary and it's just a small step along the way to
4141
true adoption and acceptance of agentic infrastructure into "the backend".
4242

43+
![Today: point-to-point integrations between workers versus one shared iii runtime, live capability discovery, and zero-integration.](../../assets/blog/the-harness-is-the-backend/point-to-point-vs-shared-runtime.png)
44+
4345
## How Agents Work Today
4446

4547
Here's how most agentic architectures work. The harness is a Python process

blog/src/layouts/BaseLayout.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import CookieBanner from '../components/CookieBanner.astro'
55
import Footer from '../components/Footer.astro'
66
import Logo from '../components/Logo.astro'
77
import ThemeInit from '../components/ThemeInit.astro'
8+
import ThemeToggle from '../components/ThemeToggle.astro'
89
910
interface Props {
1011
title: string
@@ -50,6 +51,7 @@ const canonical = new URL(Astro.url.pathname, Astro.site).toString()
5051
<a href="/blog/">all posts</a>
5152
<a href="/docs">docs</a>
5253
<a href="/blog/rss.xml" aria-label="RSS feed">rss</a>
54+
<ThemeToggle />
5355
</div>
5456
</nav>
5557
<main>

blog/src/pages/rss.xml.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,21 @@ import type { APIContext } from 'astro'
44

55
export async function GET(context: APIContext) {
66
const posts = await getCollection('blog', ({ data }) => !data.draft)
7+
// The feed lives at /blog/rss.xml with items under /blog/<slug>/.
8+
// `site` becomes the channel <link>; it must be the blog home, not the
9+
// apex site, or readers treat the feed as describing iii.dev instead of /blog.
10+
const origin = context.site ?? new URL('https://iii.dev/')
11+
const site = new URL('blog/', origin).href
12+
const selfLink = new URL('rss.xml', site).href
13+
714
return rss({
815
title: 'iii blog',
916
description: 'Notes from the team building iii — three primitives, zero integration cost.',
10-
site: context.site ?? 'https://iii.dev',
17+
site,
18+
xmlns: {
19+
atom: 'http://www.w3.org/2005/Atom',
20+
},
21+
customData: `<atom:link href="${selfLink}" rel="self" type="application/rss+xml"/>`,
1122
items: posts
1223
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
1324
.map((post) => ({

blog/src/styles/global.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,31 @@ body {
129129
color: var(--accent);
130130
}
131131

132+
/* Theme toggle — matches website navbar control */
133+
.theme-btn {
134+
display: inline-flex;
135+
align-items: center;
136+
justify-content: center;
137+
width: 30px;
138+
height: 30px;
139+
padding: 0;
140+
margin: 0;
141+
border: 1px solid var(--rule);
142+
background: var(--bg);
143+
color: var(--ink-faint);
144+
cursor: pointer;
145+
font: inherit;
146+
transition: color 0.15s, border-color 0.15s, background 0.15s;
147+
}
148+
.theme-btn:hover {
149+
color: var(--ink);
150+
border-color: var(--ink);
151+
}
152+
.theme-btn svg {
153+
width: 14px;
154+
height: 14px;
155+
}
156+
132157
/* ============= MAIN ============= */
133158
main {
134159
max-width: 760px;

blog/tests/build.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ test('blog index emits at /blog/ and links to sample post', async () => {
1313
const html = await read('index.html')
1414
assert.match(html, /href="\/blog\/add-a-worker\/"/, 'index should link to /blog/add-a-worker/')
1515
assert.match(html, /Add a worker/i, 'index should render the sample post title')
16+
assert.match(html, /id="theme-btn"/, 'index should include theme toggle (parity with iii.dev)')
1617
})
1718

1819
test('sample post emits at /blog/add-a-worker/index.html', async () => {
@@ -24,6 +25,8 @@ test('sample post emits at /blog/add-a-worker/index.html', async () => {
2425
test('rss feed exists and references the canonical post URL', async () => {
2526
const xml = await read('rss.xml')
2627
assert.match(xml, /<rss/i)
28+
assert.match(xml, /<link>https:\/\/iii\.dev\/blog\/<\/link>/, 'channel link should be blog home')
29+
assert.match(xml, /atom:link[^>]*href="https:\/\/iii\.dev\/blog\/rss\.xml"/, 'feed should advertise atom:self URL')
2730
assert.match(xml, /https:\/\/iii\.dev\/blog\/add-a-worker\//, 'rss should use absolute /blog/ URLs')
2831
})
2932

0 commit comments

Comments
 (0)