feat: relay slides (outbox model, categories, access asymmetry)

Explain the outbox/gossip model and Amethyst's relay categories, plus a
read/write asymmetry table showing why outbox / inbox / DM need
different relay sets and what breaks if a list is empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
padreug 2026-07-22 02:53:45 +02:00
commit ba9d4912ac
3 changed files with 190 additions and 0 deletions

View file

@ -0,0 +1,45 @@
<script setup lang="ts">
import { PenLine, ArrowRight, Server, Users } from '@lucide/vue'
import SlideShell from './SlideShell.vue'
</script>
<template>
<SlideShell kicker="The key idea" title="The outbox model (a.k.a. gossip)">
<!-- Flow: you write your outbox relays followers read -->
<div class="flex flex-col items-stretch gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex flex-1 flex-col items-center gap-2 rounded-xl border border-zinc-700 bg-zinc-900/60 p-5 text-center">
<PenLine class="h-8 w-8 text-violet-400" />
<p class="font-semibold text-white">You post</p>
<p class="text-sm text-zinc-400">A signed note</p>
</div>
<ArrowRight class="mx-auto h-6 w-6 rotate-90 text-zinc-500 sm:rotate-0" />
<div class="flex flex-1 flex-col items-center gap-2 rounded-xl border border-violet-600/50 bg-violet-950/20 p-5 text-center">
<Server class="h-8 w-8 text-violet-300" />
<p class="font-semibold text-white">Your <span class="text-violet-300">write / outbox</span> relays</p>
<p class="text-sm text-zinc-400">Where your content lives</p>
</div>
<ArrowRight class="mx-auto h-6 w-6 rotate-90 text-zinc-500 sm:rotate-0" />
<div class="flex flex-1 flex-col items-center gap-2 rounded-xl border border-zinc-700 bg-zinc-900/60 p-5 text-center">
<Users class="h-8 w-8 text-violet-400" />
<p class="font-semibold text-white">Followers read</p>
<p class="text-sm text-zinc-400">Their app looks there</p>
</div>
</div>
<div class="mt-8 space-y-4">
<p>
Your app <span class="font-semibold text-white">announces</span> which relays you write to.
Other peoples apps read that list and go fetch your notes <span class="text-white">from the right place</span>
no central server required.
</p>
<p class="rounded-lg border-l-2 border-violet-500 bg-zinc-900/50 py-3 pl-4 pr-3 text-zinc-300">
If you dont know where your posts are, you might as well just stay on centralized Twitter.
<span class="text-zinc-500"> Vitor Pamplona, Amethyst</span>
</p>
</div>
</SlideShell>
</template>

View file

@ -0,0 +1,70 @@
<script setup lang="ts">
import { Home, Bell, Mail } from '@lucide/vue'
import SlideShell from './SlideShell.vue'
const rows = [
{
icon: Home,
role: 'Outbox',
writes: 'You',
reads: 'Everyone',
empty: 'Followers never get your posts',
},
{
icon: Bell,
role: 'Inbox',
writes: 'Everyone',
reads: 'You',
empty: 'You miss replies & mentions',
},
{
icon: Mail,
role: 'DM inbox',
writes: 'Anyone (encrypted)',
reads: 'Only you',
empty: 'You miss DMs',
},
]
</script>
<template>
<SlideShell kicker="Amethyst — why the sets differ" title="One relay cant do every job">
<div class="overflow-hidden rounded-xl border border-zinc-700">
<table class="w-full text-left text-base">
<thead class="bg-zinc-800/60 text-sm uppercase tracking-wider text-zinc-400">
<tr>
<th class="px-4 py-3 font-medium">Role</th>
<th class="px-4 py-3 font-medium">Who writes</th>
<th class="px-4 py-3 font-medium">Who reads</th>
<th class="px-4 py-3 font-medium text-red-300">If you leave it empty</th>
</tr>
</thead>
<tbody class="divide-y divide-zinc-800">
<tr v-for="row in rows" :key="row.role">
<td class="px-4 py-3 font-semibold text-white">
<span class="flex items-center gap-2">
<component :is="row.icon" class="h-5 w-5 text-violet-400" />
{{ row.role }}
</span>
</td>
<td class="px-4 py-3 text-zinc-300">{{ row.writes }}</td>
<td class="px-4 py-3 text-zinc-300">{{ row.reads }}</td>
<td class="px-4 py-3 text-red-300/90">{{ row.empty }}</td>
</tr>
</tbody>
</table>
</div>
<p class="mt-6">
The access rules are <span class="font-semibold text-white">opposite</span>, so no single relay
fits every list thats the whole reason for separate sets.
</p>
<div class="mt-4 rounded-lg border-l-2 border-violet-500 bg-zinc-900/50 py-3 pl-4 pr-3 text-zinc-300">
<span class="font-semibold text-white">Reuse vs. keep separate:</span>
an <em>open</em> relay (e.g. <code class="text-zinc-200">nos.lol</code>) is fine in several lists at once.
A <em>paid / restricted</em> relay (e.g. <code class="text-zinc-200">nostr.wine</code>) must stay in its lane
great for DMs, wrong for your public inbox.
</div>
</SlideShell>
</template>

View file

@ -0,0 +1,75 @@
<script setup lang="ts">
import { Home, Bell, Lock, Mail, Search, HardDrive } from '@lucide/vue'
import SlideShell from './SlideShell.vue'
const categories = [
{
icon: Home,
name: 'Public Home / Outbox',
count: '~3',
what: 'Where your posts, likes & replies live. Followers read from here.',
tip: 'One big public relay + one personal + one fast/country relay.',
},
{
icon: Bell,
name: 'Public Inbox',
count: '~3',
what: 'Where replies, mentions & zaps arrive — your notifications.',
tip: 'Keep separate from outbox. Avoid nostr.wine variants here.',
},
{
icon: Mail,
name: 'DM Inbox',
count: '~3',
what: 'Encrypted direct messages. Write-open, read-restricted to you.',
tip: 'inbox.nostr.wine or auth.nostr1.com + a personal backup.',
},
{
icon: Lock,
name: 'Private Home',
count: '12',
what: 'Drafts, bookmarks, lists & app settings — nobody else reads these.',
tip: 'A local relay (Citrine) or an authed personal relay.',
},
{
icon: Search,
name: 'Search',
count: '23',
what: 'Powers tagging & search. Must support NIP-50.',
tip: 'relay.nostr.band, nostr.wine, relay.noswhere.com.',
},
{
icon: HardDrive,
name: 'Local',
count: '1',
what: 'On-device cache so the app is fast & works offline.',
tip: 'Citrine on Android → ws://localhost:4869.',
},
]
</script>
<template>
<SlideShell kicker="Amethyst — the confusing part" title="Relays come in categories">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div
v-for="cat in categories"
:key="cat.name"
class="rounded-xl border border-zinc-700 bg-zinc-900/60 p-5"
>
<div class="mb-2 flex items-center justify-between">
<component :is="cat.icon" class="h-6 w-6 text-violet-400" />
<span class="rounded-full bg-violet-600/20 px-2 py-0.5 text-xs font-semibold text-violet-300">
{{ cat.count }}
</span>
</div>
<p class="text-lg font-semibold text-white">{{ cat.name }}</p>
<p class="mt-1 text-sm leading-snug text-zinc-400">{{ cat.what }}</p>
<p class="mt-3 text-xs leading-snug text-violet-300/90"> {{ cat.tip }}</p>
</div>
</div>
<p class="mt-6 text-sm text-zinc-500">
Dont overthink it: set <span class="text-zinc-300">outbox</span>, <span class="text-zinc-300">inbox</span> and
<span class="text-zinc-300">DM</span> with ~3 solid relays each. Amethyst suggests the rest from who you follow.
</p>
</SlideShell>
</template>