flotilla/docs/feature_matrix.html

711 lines
23 KiB
HTML
Raw Normal View History

2026-09-11 23:47:17 +00:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flotilla feature matrix</title>
<style>
:root {
color-scheme: light dark;
--fg: #1a1a1a;
--muted: #5b6470;
--bg: #ffffff;
--surface: #f6f7f9;
--border: #dfe3e8;
--accent: #6d28d9;
--ok: #166534;
--ok-bg: #dcfce7;
--partial: #92400e;
--partial-bg: #fef3c7;
--no: #991b1b;
--no-bg: #fee2e2;
--design: #1e40af;
--design-bg: #dbeafe;
}
@media (prefers-color-scheme: dark) {
:root {
--fg: #e6e8eb;
--muted: #9aa4b2;
--bg: #15171a;
--surface: #1d2025;
--border: #30353c;
--accent: #c4b5fd;
--ok: #86efac;
--ok-bg: #14311f;
--partial: #fcd34d;
--partial-bg: #3a2c0a;
--no: #fca5a5;
--no-bg: #3a1414;
--design: #93c5fd;
--design-bg: #12233f;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
main {
max-width: 1100px;
margin: 0 auto;
padding: 48px 24px 96px;
}
h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 1rem; }
h2 {
font-size: 1.4rem;
margin: 3rem 0 1rem;
padding-bottom: 0.4rem;
border-bottom: 2px solid var(--border);
}
h3 { font-size: 1.1rem; margin: 2rem 0 0.75rem; }
p { margin: 0.75rem 0; }
a { color: var(--accent); }
code {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.1em 0.35em;
font: 0.875em/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
ul { padding-left: 1.4rem; }
li { margin: 0.35rem 0; }
.table-wrap { overflow-x: auto; margin: 1rem 0 1.5rem; }
table {
border-collapse: collapse;
width: 100%;
font-size: 0.95rem;
}
th, td {
border: 1px solid var(--border);
padding: 8px 12px;
text-align: left;
vertical-align: top;
}
th {
background: var(--surface);
font-weight: 600;
white-space: nowrap;
}
tbody tr:nth-child(even) { background: color-mix(in srgb, var(--surface) 55%, transparent); }
.status {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
white-space: nowrap;
}
.s-ok { color: var(--ok); background: var(--ok-bg); }
.s-partial { color: var(--partial); background: var(--partial-bg); }
.s-no { color: var(--no); background: var(--no-bg); }
.s-design { color: var(--design); background: var(--design-bg); }
hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
</style>
</head>
<body>
<main>
<h1>Flotilla feature matrix</h1>
<p>Flotilla&#39;s feature set, grouped by the areas of the pasted outline and expanded
with capabilities found in the codebase. Status values:</p>
<ul>
<li><strong>Implemented</strong> — reachable in the app today.</li>
<li><strong>Partial</strong> — present but limited, gated behind configuration, or dependent on
the relay/native platform.</li>
<li><strong>Not implemented</strong> — no code path exists.</li>
<li><strong>By design</strong> — supplied by the Nostr protocol or the deployment model rather
than by app code.</li>
</ul>
<p>Notes point at the route or module that carries the feature; <code>US-nnn</code> refers to
the story catalog in <code>e2e/USER_STORIES.md</code>.</p>
<h2>Chat</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Spaces (relay-based communities)</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces</code>, <code>app/rooms.ts</code>; NIP-29 relay-as-group. US-009–017</td>
</tr>
<tr>
<td>Rooms / channels</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/[h]</code>, <code>RoomChat.svelte</code>. US-018–028</td>
</tr>
<tr>
<td>Direct messages</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/chat</code>, <code>app/chats.ts</code>; NIP-17/44/59. US-029–036, US-108–109</td>
</tr>
<tr>
<td>Group DMs</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ChatStart.svelte</code>, <code>ChatMembers.svelte</code>. US-030</td>
</tr>
<tr>
<td>Voice &amp; video calls</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/call.ts</code>, <code>app/callEngine.ts</code>, LiveKit; relay must advertise the endpoint. Calls and video are out of e2e scope</td>
</tr>
<tr>
<td>Push notifications</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/push/</code>, <code>settings/alerts</code>; web, FCM/APNs and Android fallback. US-086, US-120, US-136</td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>Threads</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/threads</code>, <code>ThreadBoard.svelte</code>. US-042–045</td>
</tr>
<tr>
<td>Media / file sharing</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/uploads.ts</code>, Blossom servers; upload, drag/paste, native clipboard. US-057, US-062</td>
</tr>
<tr>
<td>Room create / edit / delete</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>RoomCreate.svelte</code>, <code>RoomEdit.svelte</code>. US-020</td>
</tr>
<tr>
<td>Room join / leave and member lists</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>RoomMembers.svelte</code>; private-room access requests in <code>RoomAccess.svelte</code>. US-019, US-021, US-022</td>
</tr>
<tr>
<td>Replies, edits and deletes</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ComposeParent.svelte</code>; up-arrow edit, US-023, US-024, US-035</td>
</tr>
<tr>
<td>Reactions</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>EventReactButtons.svelte</code>, <code>app/reactions.ts</code>. US-025, US-040</td>
</tr>
<tr>
<td>Pinned messages</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>RoomPinnedMessages.svelte</code>, <code>app/roomPins.ts</code>. US-026</td>
</tr>
<tr>
<td>Forward / share to another room</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>Share.svelte</code>, <code>ShareEvent.svelte</code>. US-028, US-106</td>
</tr>
<tr>
<td>Drafts</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/drafts.ts</code>. US-058</td>
</tr>
<tr>
<td>Read out loud and dictation</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>SpeechBanner.svelte</code>, <code>app/dictation.ts</code>, <code>app/speech.ts</code>; OpenRouter key. US-119</td>
</tr>
<tr>
<td>Room and space search</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>RoomSearch.svelte</code>, <code>SpaceSearch.svelte</code>. US-017, US-027</td>
</tr>
<tr>
<td>Mentions, room references, hashtags, custom emoji</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>@welshman/editor</code> suggestions, <code>ContentMention.svelte</code>, <code>ContentTopic.svelte</code>, <code>ContentEmoji.svelte</code>. US-056, US-066</td>
</tr>
<tr>
<td>Link previews and rich embeds</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ContentLinkBlock.svelte</code>, <code>NoteContent*.svelte</code>; notes, articles, threads, images, video, invoices/Cashu. US-062–067</td>
</tr>
<tr>
<td>Send delay, per-relay delivery status, cancel and retry</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ThunkStatus.svelte</code>, <code>app/thunks.ts</code>. US-068–073</td>
</tr>
<tr>
<td>Zaps on messages</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>Zap.svelte</code>, <code>app/lightning.ts</code>; NIP-57. US-115</td>
</tr>
<tr>
<td>Unread indicators and mute</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/notifications.ts</code>. US-103, US-104, US-110–114</td>
</tr>
</tbody></table>
<h2>Events</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Calendar events</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/calendar</code>, <code>CalendarEvent*.svelte</code>; NIP-52. US-046–047</td>
</tr>
<tr>
<td>Monthly calendar view</td>
2026-09-15 21:08:39 +00:00
<td><span class="status s-ok">Implemented</span></td>
<td><code>CalendarMonth.svelte</code>, <code>CalendarWeek.svelte</code>, <code>CalendarAgenda.svelte</code>; month, week and agenda views, remembered per user</td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
2026-09-15 21:08:39 +00:00
<td>RSVPs</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>CalendarRsvp.svelte</code>, <code>app/calendar.ts</code>; NIP-52 going, maybe and can&#39;t go. No ticketing or paid attendance</td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>Event detail with location and host</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>CalendarEventMeta.svelte</code>, <code>CalendarEventHeader.svelte</code>. US-047</td>
</tr>
<tr>
<td>Comments and reactions on events</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>EventComments.svelte</code>, <code>EventReactButtons.svelte</code>. US-052</td>
</tr>
</tbody></table>
<h2>Community content</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Long-form articles</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/articles</code>, <code>app/articles.ts</code>; NIP-23. US-037–039, US-041</td>
</tr>
<tr>
<td>Polls</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/polls</code>, <code>PollCreate.svelte</code>; NIP-88, single- and multiple-choice. US-048–049</td>
</tr>
<tr>
<td>Funding goals</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/goals</code>, <code>GoalCreate.svelte</code>; zap goal kind. US-050, US-052</td>
</tr>
<tr>
<td>Comments and reactions across content</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>CommentTree.svelte</code>, <code>EventActivity.svelte</code>. US-039, US-052</td>
</tr>
<tr>
<td>Create-from-room composer menu</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ComposeMenu.svelte</code>; publishes a kind-9 quote for interoperability. US-041, US-055</td>
</tr>
</tbody></table>
<h2>Moderation</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Ban / unban</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>SpaceMemberMenu.svelte</code>, <code>SpaceMembersBanned.svelte</code>; NIP-86 <code>banpubkey</code> / <code>unbanpubkey</code>. US-095</td>
</tr>
<tr>
<td>Member management</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/directory</code>, <code>SpaceMembersAdd.svelte</code>, <code>RoomMembersAdd.svelte</code>. US-022, US-095</td>
</tr>
<tr>
<td>Invite codes / access control</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/access.ts</code>, <code>SpaceInvite.svelte</code>, <code>RoomInvite.svelte</code>; relay claims and scoped codes. US-010–011, US-094</td>
</tr>
<tr>
<td>Role management and badges</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/directory</code>, <code>app/roles.ts</code>, <code>SpaceRoles.svelte</code>; NIP-29 <code>member</code> tags. US-093</td>
</tr>
<tr>
<td>Granular RBAC / role permissions</td>
2026-09-15 21:08:39 +00:00
<td><span class="status s-ok">Implemented</span></td>
<td>Roles are assigned in <code>/spaces/[relay]/directory</code>; a relay extension enforces the per-role permissions</td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>Reports and content moderation</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>Report*.svelte</code>, <code>app/actionItems.ts</code>; author deletes, member reports, admin removes. US-096</td>
</tr>
<tr>
<td>Action-items queue</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>SpaceActionItems.svelte</code>, <code>app/actionItems.ts</code>. US-097</td>
</tr>
<tr>
<td>Relay policy warnings</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>RelaySummary.svelte</code>, <code>app/policies.ts</code>; auth-required, payment-required, proof-of-work. US-012, US-015</td>
</tr>
<tr>
<td>Unsigned-relay trust prompt</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>SpaceTrustRelay.svelte</code>. US-012</td>
</tr>
<tr>
<td>Mute rooms and spaces</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/settings.ts</code>, <code>RoomDetail.svelte</code>. US-104</td>
</tr>
<tr>
<td>Feature / pin content as admin</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>EditFeaturedContent.svelte</code>, <code>app/featured.ts</code>. US-092</td>
</tr>
</tbody></table>
<h2>Member tools</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Member profiles</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/people/[npub]</code>, <code>ProfilePage.svelte</code>. US-075, US-080, US-081</td>
</tr>
<tr>
<td>Edit own profile</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfileEditForm.svelte</code>, <code>settings/profile</code>. US-078</td>
</tr>
<tr>
<td>Searchable directory</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/directory</code>; search by name or role. US-093</td>
</tr>
<tr>
<td>People search</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>Search.svelte</code>, <code>app/social.ts</code>. US-074</td>
</tr>
<tr>
<td>Classifieds</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/classifieds</code>, <code>ClassifiedForm.svelte</code>; NIP-99, sold status. US-051</td>
</tr>
<tr>
<td>Content library / pinboards</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces/[relay]/library</code>, <code>Pinboard*.svelte</code>, <code>app/pinboards.ts</code>. US-053–054</td>
</tr>
<tr>
<td>Follow / unfollow</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfileMenu.svelte</code>, follow list. US-076</td>
</tr>
<tr>
<td>Web of trust</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfileTrust.svelte</code>, <code>app/social.ts</code>. US-077</td>
</tr>
<tr>
<td>Mute accounts</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>settings/content</code>. US-082</td>
</tr>
<tr>
<td>Profile notes feed</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfilePageNotes.svelte</code>. US-079</td>
</tr>
<tr>
<td>Profile sharing, QR and raw info</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfileQrCode.svelte</code>, <code>ProfileInfo.svelte</code>, <code>InfoNostr.svelte</code>. US-081</td>
</tr>
<tr>
<td>Statuses</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfileStatus.svelte</code>; NIP-38. US-075</td>
</tr>
</tbody></table>
<h2>White-labeling</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Branded web app</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>VITE_PLATFORM_*</code> env, <code>app/env.ts</code>; name, url, logo, accent, description, terms, privacy</td>
</tr>
<tr>
<td>Branded Android app</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>android/</code>, <code>@capacitor/assets</code>, <code>pnpm release:local</code></td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>Branded iOS app</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ios/</code>, Capacitor build</td>
</tr>
<tr>
<td>Branded desktop app</td>
2026-09-15 21:08:39 +00:00
<td><span class="status s-ok">Implemented</span></td>
<td><code>electron/</code>, <code>scripts/desktop/package.mjs</code>; <code>pnpm run package:desktop:linux</code> / <code>:windows</code> / <code>:macos</code></td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>PWA / installable web app</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>@vite-pwa/sveltekit</code>, <code>manifest.webmanifest</code>, <code>service-worker.js</code></td>
</tr>
<tr>
<td>Push to your own app</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>VITE_PUSH_SERVER</code> / <code>VITE_PUSH_BRIDGE</code>, <code>app/push/</code></td>
</tr>
<tr>
<td>Custom domain</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>CustomDomainModal.svelte</code>, <code>app/hosting.ts</code>; CNAME verification for hosted relays. US-101</td>
</tr>
<tr>
<td>Platform mode (own relays only)</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>VITE_PLATFORM_RELAYS</code>; disables browse/add/select and makes the platform relay home</td>
</tr>
<tr>
<td>Configurable defaults</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>VITE_DEFAULT_SPACES</code>, <code>VITE_DEFAULT_RELAYS</code>, <code>VITE_DEFAULT_MESSAGING_RELAYS</code>, <code>VITE_DEFAULT_BLOSSOM_SERVERS</code></td>
</tr>
<tr>
<td>Native share sheet and deep links</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>@capacitor/share</code>, Android share intent, <code>nostr:</code> / <code>https:</code> dispatch. US-106, US-107</td>
</tr>
</tbody></table>
<h2>Self-hosting</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Self-hostable</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>Dockerfile</code>, <code>server.js</code>; <code>docker run</code> or static build copied to any host</td>
</tr>
<tr>
<td>Open source</td>
<td><span class="status s-ok">Implemented</span></td>
<td>MIT license (<code>LICENSE</code>)</td>
</tr>
<tr>
<td>Static-host deployment</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>pnpm run build</code> output can be copied to any static host (<code>README.md</code>)</td>
</tr>
<tr>
<td>Managed hosting platform</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>app/hosting.ts</code>, <code>settings/hosting</code>, <code>/spaces/[relay]/admin</code>; create, plan, domain, pause and billing. US-098–102</td>
</tr>
<tr>
<td>Relay health checks</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>HomeHealthChecks.svelte</code>, <code>app/healthChecks.ts</code>. US-116</td>
</tr>
</tbody></table>
<h2>SSO</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Email / OTP sign-in</td>
2026-09-15 21:08:39 +00:00
<td><span class="status s-ok">Implemented</span></td>
<td><code>LogInEmail.svelte</code>, <code>LogInOTP.svelte</code>, <code>app/pomade.ts</code>; custodial Pomade flow, enabled when <code>VITE_POMADE_SIGNERS</code> is set. Out of e2e scope</td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>Browser extension sign-in</td>
<td><span class="status s-ok">Implemented</span></td>
<td>NIP-07, <code>LogInSelect.svelte</code>. US-004</td>
</tr>
<tr>
<td>Remote signer / bunker</td>
<td><span class="status s-ok">Implemented</span></td>
<td>NIP-46, <code>LogInBunker.svelte</code>, <code>BunkerConnect.svelte</code>, QR flow. US-005</td>
</tr>
<tr>
<td>Native Android signer</td>
<td><span class="status s-ok">Implemented</span></td>
<td>NIP-55; out of e2e scope</td>
</tr>
<tr>
<td>Relay authentication</td>
<td><span class="status s-ok">Implemented</span></td>
<td>NIP-42, <code>app/access.ts</code>, <code>settings/privacy</code></td>
</tr>
<tr>
<td>Key backup on signup</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>KeyDownload.svelte</code>; plain nsec or password-encrypted ncryptsec. US-002</td>
</tr>
<tr>
<td>Data export</td>
2026-09-15 21:08:39 +00:00
<td><span class="status s-ok">Implemented</span></td>
<td><code>hosting/DataTransferModal.svelte</code>, <code>app/hosting.ts</code>; a space&#39;s events download as JSONL and re-import into another relay</td>
2026-09-11 23:47:17 +00:00
</tr>
<tr>
<td>Account deletion</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>ProfileDelete.svelte</code>, <code>settings/profile</code>; broadcasts account deletion. US-008</td>
</tr>
</tbody></table>
<h2>Portable identity &amp; history</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Customer holds keys</td>
<td><span class="status s-design">By design</span></td>
<td>Identity is a self-sovereign <code>nsec</code>/<code>npub</code>; no account lives with the operator</td>
</tr>
<tr>
<td>Customer holds source and data</td>
<td><span class="status s-design">By design</span></td>
<td>Self-hostable relay plus user-chosen relays; events are portable Nostr events</td>
</tr>
<tr>
<td>One-click export / import</td>
<td><span class="status s-no">Not implemented</span></td>
<td>No bundled export or re-import of a user&#39;s data</td>
</tr>
<tr>
<td>Multi-device sync</td>
<td><span class="status s-ok">Implemented</span></td>
<td>NIP-78 app-data sync, <code>app/sync.ts</code>; read state and settings follow the account</td>
</tr>
<tr>
<td>Local-first history</td>
<td><span class="status s-ok">Implemented</span></td>
<td>IndexedDB persistence, <code>app/storage.ts</code>, <code>lib/indexeddb.ts</code>; read conversations survive relay loss. US-109</td>
</tr>
<tr>
<td>Account portability across clients</td>
<td><span class="status s-design">By design</span></td>
<td>The same keys and relay events work in any NIP-29 client</td>
</tr>
<tr>
<td>Key recovery</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>KeyRecoveryRequest.svelte</code>, <code>KeyRecoveryConfirm.svelte</code> for custodial (Pomade) accounts</td>
</tr>
</tbody></table>
<h2>Home &amp; discovery</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Home dashboard / inbox</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/home</code>, <code>HomeInbox.svelte</code>, <code>app/inbox.ts</code>. US-105, US-116</td>
</tr>
<tr>
<td>Cross-space activity summary</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>HomeActivity.svelte</code>, <code>app/feeds.ts</code>. US-116</td>
</tr>
<tr>
<td>Network feed of follows</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>HomeNetwork.svelte</code>, <code>app/feeds.ts</code>. US-117</td>
</tr>
<tr>
<td>Space browse, search and reorder</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>/spaces</code>, <code>PrimaryNavSpaces.svelte</code>, drag reordering. US-009</td>
</tr>
<tr>
<td>Join by invite / direct link</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>SpaceJoin.svelte</code>, <code>/join</code>, invite parsing in <code>app/access.ts</code>. US-010, US-011</td>
</tr>
<tr>
<td>Link and bech32 resolution</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>[bech32]</code> route, <code>app/routes.ts</code>; npub/nevent/note. US-107</td>
</tr>
</tbody></table>
<h2>Lightning &amp; payments</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Zap messages and profiles</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>Zap.svelte</code>, <code>app/lightning.ts</code>; NIP-57</td>
</tr>
<tr>
<td>Wallet connection (NWC / WebLN)</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>settings/wallet</code>, <code>app/lightning.ts</code>; NIP-47. US-115</td>
</tr>
<tr>
<td>Lightning address and zap presets</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>settings/wallet</code>, <code>app/lightning.ts</code>. US-091</td>
</tr>
<tr>
<td>Hosting billing and invoices</td>
<td><span class="status s-ok">Implemented</span></td>
<td><code>PaymentDialog.svelte</code>, <code>PaymentSetup.svelte</code>, <code>app/hosting.ts</code>; Lightning and card. US-102</td>
</tr>
</tbody></table>
</main>
</body>
</html>