Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions site/src/components/blog/HonestVault.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
// The honesty beat for post #11 (exploration 0281), modeled on HonestWorkshop /
// HonestTiller / HonestGarden. An essay that says "apps should be views over
// data you own" is standing on a pile of projects that said the same thing and
// stalled — Solid pods, WinFS, the semantic desktop — so it owes the reader a
// plain account of why they stalled and what this attempt does differently.
// The rows concede the strongest standing objections rather than dodging them.
const rows = [
{
isnt: 'We won’t pretend two apps agreeing on data is a solved problem.',
is: 'Schema agreement is the hard part — Solid’s own researchers concluded that apps failed to reuse each other’s data because each one baked in private assumptions about layout. Our answer is not a grand shared ontology; it’s published, versioned schemas plus lenses, overlays, and sidecars that make disagreement cheap. Views can differ about shape without forking the data.'
},
{
isnt: 'We won’t pretend a general-purpose store is fast for free.',
is: 'Purpose-built silos are quick because they optimise for exactly one access pattern. Making one open substrate feel app-fast took us months of unglamorous engineering — a dozen pull requests on query batching, hydration, and cold-start alone. The convenience of the vault is real; it just isn’t worth the walls.'
},
{
isnt: 'We won’t pretend decoupling data from apps removes the need for trust.',
is: 'A view still runs with access to the slice it renders, and a malicious view is still malicious. That’s a different essay — the previous one, in fact — but the short version is: every view here declares what it can see and touch, and the declaration is enforced, not promised.'
},
{
isnt: 'We won’t pretend decentralisation can’t recentralise one layer up.',
is: 'Gordon Brander’s law is real: unbundle the data layer and power tends to reappear at the index, the relay, the host. Our hedge is structural — the local replica is primary, the hub is an optional peer you can replace, and leaving with everything is a supported, documented act rather than a scraping exercise.'
}
]
---

<section class="not-prose my-12">
<div class="rounded-2xl border border-border bg-surface/30 p-6 dark:bg-surface/40 lg:p-8">
<h3 class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">
An honest inversion
</h3>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
The graveyard of systems that promised this is well populated. Here’s
what the residents taught us, and where the romance stops.
</p>

<ul class="mt-6 space-y-5">
{
rows.map((row) => (
<li class="grid gap-3 sm:grid-cols-[1fr_1.3fr] sm:gap-5">
<div class="flex gap-2.5">
<span class="select-none text-gray-400 dark:text-gray-600">✕</span>
<p class="text-sm leading-relaxed text-gray-500 dark:text-gray-400">{row.isnt}</p>
</div>
<div class="flex gap-2.5">
<span class="select-none text-sky-500">✓</span>
<p class="text-sm leading-relaxed text-gray-700 dark:text-gray-200">{row.is}</p>
</div>
</li>
))
}
</ul>
</div>
</section>
208 changes: 208 additions & 0 deletions site/src/components/blog/VaultArt.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
---
// Decorative art for blog post #11 (exploration 0281), extracted so the same
// inline SVG backs both the post hero and the blog-index preview card (no
// external assets — Self-Audit parity). The scene is the essay's title drawn
// literally: on the left, a bank vault — a sealed circular door with a dial
// and rivets, and a small barred slot behind which a few dim data points sit
// locked away. On the right, the inversion: one glowing hash-chained substrate
// running along the ground, holding the same points in the open, with three
// floating window frames above it — list, grid, timeline — each looking onto
// the same data and drawing sight-lines down to it. The cosmic-X hangs
// overhead as the reading lamp. A dotted footpath runs from the vault to the
// substrate: the walk from custody to ownership.
interface Props {
/** Override the wrapper classes; defaults to full-bleed for hero use. */
class?: string
}
const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
Astro.props

// Rivets around the vault door rim (angle in degrees).
const rivets = Array.from({ length: 12 }, (_, i) => {
const a = (i * 30 * Math.PI) / 180
return [170 + Math.cos(a) * 96, 190 + Math.sin(a) * 96]
})
// The hash-chained substrate blocks along the ground on the right.
const chain = [430, 520, 610, 700, 790, 880]
// The footpath from the vault out to the substrate.
const path = [
[292, 306],
[340, 300],
[388, 302],
[430, 314]
]
.map(([x, y]) => `${x},${y}`)
.join(' ')
// The shared data points sitting on the substrate (x, y).
const nodes = [
[455, 262],
[545, 250],
[635, 266],
[725, 248],
[815, 260],
[905, 252]
]
---

<svg
class={className}
viewBox="0 0 1040 340"
preserveAspectRatio="xMidYMid slice"
aria-hidden="true"
>
<defs>
<linearGradient id="vaultbg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0a1122" />
<stop offset="55%" stop-color="#0d1526" />
<stop offset="100%" stop-color="#080d1a" />
</linearGradient>
<radialGradient id="vaultlampglow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#7dd3fc" stop-opacity="0.95" />
<stop offset="45%" stop-color="#38bdf8" stop-opacity="0.38" />
<stop offset="100%" stop-color="#38bdf8" stop-opacity="0" />
</radialGradient>
<linearGradient id="vaultlampx" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#e0f2fe" />
<stop offset="100%" stop-color="#38bdf8" />
</linearGradient>
<linearGradient id="substrateglow" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#38bdf8" stop-opacity="0.16" />
<stop offset="100%" stop-color="#38bdf8" stop-opacity="0" />
</linearGradient>
<linearGradient id="vaultsteel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1d2a44" />
<stop offset="100%" stop-color="#131d33" />
</linearGradient>
</defs>

<rect width="1040" height="340" fill="url(#vaultbg)" />

<!-- ── the cosmic-X reading lamp, top-right, clear of the title block ── -->
<circle cx="880" cy="58" r="60" fill="url(#vaultlampglow)" />
<g stroke="url(#vaultlampx)" stroke-width="7" stroke-linecap="round">
<line x1="863" y1="41" x2="897" y2="75" />
<line x1="897" y1="41" x2="863" y2="75" />
</g>

<!-- ── left: the vault ──────────────────────────────────────────────── -->
<!-- door frame -->
<circle cx="170" cy="190" r="108" fill="url(#vaultsteel)" stroke="#2b3b5e" stroke-width="3" />
<circle cx="170" cy="190" r="96" fill="none" stroke="#2b3b5e" stroke-width="2" />
{rivets.map(([x, y]) => <circle cx={x} cy={y} r="3.2" fill="#3b4f79" />)}
<!-- inner door + dial -->
<circle cx="170" cy="190" r="72" fill="#101a2f" stroke="#2b3b5e" stroke-width="2.5" />
<circle cx="170" cy="190" r="24" fill="none" stroke="#466195" stroke-width="3" />
<g stroke="#466195" stroke-width="3" stroke-linecap="round">
<line x1="170" y1="166" x2="170" y2="150" />
<line x1="170" y1="214" x2="170" y2="230" />
<line x1="146" y1="190" x2="130" y2="190" />
<line x1="194" y1="190" x2="210" y2="190" />
<line x1="153" y1="173" x2="142" y2="162" />
<line x1="187" y1="207" x2="198" y2="218" />
<line x1="187" y1="173" x2="198" y2="162" />
<line x1="153" y1="207" x2="142" y2="218" />
</g>
<!-- the barred slot: three dim points locked behind it -->
<rect x="138" y="112" width="64" height="26" rx="5" fill="#0b1322" stroke="#2b3b5e" stroke-width="2" />
<g fill="#38bdf8" opacity="0.35">
<circle cx="152" cy="125" r="3.4" />
<circle cx="170" cy="125" r="3.4" />
<circle cx="188" cy="125" r="3.4" />
</g>
<g stroke="#2b3b5e" stroke-width="2.4">
<line x1="154" y1="112" x2="154" y2="138" />
<line x1="170" y1="112" x2="170" y2="138" />
<line x1="186" y1="112" x2="186" y2="138" />
</g>
<!-- ground shadow -->
<ellipse cx="170" cy="304" rx="112" ry="9" fill="#060b16" />

<!-- ── the footpath from custody to ownership ───────────────────────── -->
<polyline
points={path}
fill="none"
stroke="#38bdf8"
stroke-opacity="0.45"
stroke-width="2.5"
stroke-dasharray="1 10"
stroke-linecap="round"
/>

<!-- ── right: one substrate, many windows ───────────────────────────── -->
<!-- glow rising off the substrate -->
<rect x="410" y="220" width="520" height="90" fill="url(#substrateglow)" />
<!-- hash-chained blocks along the ground -->
<g>
{
chain.map((x, i) => (
<>
<rect
x={x}
y="288"
width="66"
height="22"
rx="5"
fill="#101a2f"
stroke="#38bdf8"
stroke-opacity="0.55"
stroke-width="1.6"
/>
{i < chain.length - 1 && (
<line
x1={x + 66}
y1="299"
x2={x + 90}
y2="299"
stroke="#38bdf8"
stroke-opacity="0.5"
stroke-width="2"
/>
)}
</>
))
}
</g>
<!-- the shared points, out in the open (dimmed where prose sits above) -->
<g fill="#7dd3fc" opacity="0.55">
{nodes.map(([x, y]) => <circle cx={x} cy={y} r="3.8" />)}
</g>
<g stroke="#38bdf8" stroke-opacity="0.22" stroke-width="1.3">
{nodes.map(([x, y]) => <line x1={x} y1={y + 6} x2={x} y2="288" />)}
</g>

<!-- three window frames in the clear right column, each a different
arrangement of the same points: list, grid, timeline -->
<!-- window 1: a list -->
<g>
<rect x="704" y="88" width="100" height="76" rx="10" fill="#0d1729" stroke="#38bdf8" stroke-opacity="0.5" stroke-width="1.8" />
<g stroke="#7dd3fc" stroke-opacity="0.7" stroke-width="3" stroke-linecap="round">
<line x1="722" y1="108" x2="784" y2="108" />
<line x1="722" y1="126" x2="772" y2="126" />
<line x1="722" y1="144" x2="780" y2="144" />
</g>
<line x1="754" y1="164" x2="770" y2="242" stroke="#38bdf8" stroke-opacity="0.22" stroke-width="1.4" />
</g>
<!-- window 2: a grid -->
<g>
<rect x="820" y="176" width="100" height="76" rx="10" fill="#0d1729" stroke="#38bdf8" stroke-opacity="0.5" stroke-width="1.8" />
<g fill="#7dd3fc" opacity="0.75">
<circle cx="848" cy="200" r="4.6" />
<circle cx="870" cy="200" r="4.6" />
<circle cx="892" cy="200" r="4.6" />
<circle cx="848" cy="228" r="4.6" />
<circle cx="870" cy="228" r="4.6" />
<circle cx="892" cy="228" r="4.6" />
</g>
<line x1="870" y1="252" x2="885" y2="288" stroke="#38bdf8" stroke-opacity="0.22" stroke-width="1.4" />
</g>
<!-- window 3: a timeline, hovering low over the chain it reads -->
<g>
<rect x="700" y="252" width="100" height="60" rx="10" fill="#0d1729" stroke="#38bdf8" stroke-opacity="0.5" stroke-width="1.8" />
<line x1="714" y1="286" x2="786" y2="286" stroke="#38bdf8" stroke-opacity="0.5" stroke-width="1.5" />
<g fill="#7dd3fc" opacity="0.75">
<circle cx="726" cy="286" r="4.4" />
<circle cx="750" cy="268" r="4.4" />
<circle cx="774" cy="286" r="4.4" />
</g>
</g>
</svg>
38 changes: 38 additions & 0 deletions site/src/components/blog/VaultHero.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
// Hero band for blog post #11 (exploration 0281). The decorative art lives in
// VaultArt.astro so the same inline SVG backs both this hero and the
// blog-index preview card; this component layers the post's title/deck over it.
import VaultArt from './VaultArt.astro'

interface Props {
title: string
deck: string
date: string
readingMinutes: number
tags: string[]
}
const { title, deck, date, readingMinutes, tags } = Astro.props
---

<section class="relative overflow-hidden border-b border-border bg-surface/20 dark:bg-[#080d1a]">
<VaultArt />

<div class="relative mx-auto max-w-3xl px-6 pb-14 pt-44 lg:pt-52">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-sky-200/80">
<time datetime={date}>{date}</time>
<span aria-hidden="true">·</span>
<span>{readingMinutes} min read</span>
{
tags.map((tag) => (
<span class="rounded-full border border-sky-400/30 bg-sky-500/10 px-2 py-0.5 font-mono lowercase text-sky-200">
{tag}
</span>
))
}
</div>
<h1 class="mt-4 text-4xl font-bold tracking-tight text-white drop-shadow lg:text-5xl">
{title}
</h1>
<p class="mt-4 max-w-2xl text-lg leading-relaxed text-gray-300">{deck}</p>
</div>
</section>
18 changes: 18 additions & 0 deletions site/src/data/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ export interface BlogPost {
}

export const posts: BlogPost[] = [
{
slug: 'the-vault-and-the-view',
title: 'The Vault and the View',
description:
'When Google Reader died, everyone got an export — and discovered the ' +
'file was a brick, shaped for a renderer that no longer existed. The ' +
'modern app is a vault: it holds your data and the only window onto it. ' +
'But the vault is a twenty-five-year detour, not the tradition — from ' +
'Codd’s data independence through Solid’s pods to local-first and “apps ' +
'as views, not vaults”, five decades of people have insisted the data is ' +
'the ground and the software is the weather. On that lineage, why the ' +
'first pod-shaped attempt stalled, how xNet ships the inversion — and ' +
'why AI-cheap views make user-owned data the only stable ground left.',
pubDate: '2026-07-07T21:00:00Z',
authors: ['crs48', 'claude'],
tags: ['essay', 'philosophy', 'decentralization', 'protocol'],
readingMinutes: 14
},
{
slug: 'the-workshop-and-the-walled-garden',
title: 'The Workshop and the Walled Garden',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "2026-07-07-new-essay-the-vault-and-the-view",
"date": "July 7, 2026",
"title": "New essay: The Vault and the View",
"summary": "Blog post #11 traces the fifty-year lineage of apps-as-views-over-your-data — from Codd's data independence through Solid's pods and local-first to xNet's substrate — and why AI-cheap views make user-owned data the ground that's left.",
"highlights": [],
"tags": ["platform"]
}
2 changes: 2 additions & 0 deletions site/src/pages/blog/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import LoomArt from '../../components/blog/LoomArt.astro'
import HookArt from '../../components/blog/HookArt.astro'
import TillerArt from '../../components/blog/TillerArt.astro'
import WorkshopArt from '../../components/blog/WorkshopArt.astro'
import VaultArt from '../../components/blog/VaultArt.astro'
import Byline from '../../components/blog/Byline.astro'
import { publishedPosts, formatPostDate } from '../../data/blog'

Expand All @@ -20,6 +21,7 @@ const posts = publishedPosts()
// Each post's art-directed hero art doubles as its preview-card header image, so
// the same inline SVG is single-sourced from the hero's *Art.astro component.
const heroArt: Record<string, typeof PirateArt> = {
'the-vault-and-the-view': VaultArt,
'the-workshop-and-the-walled-garden': WorkshopArt,
'hand-on-the-tiller': TillerArt,
'the-tip-of-the-hook': HookArt,
Expand Down
Loading
Loading