{#if hue !== null} {/if}
{#if key} {:else} {/if}
{profile?.display_name || (key ? "You" : "You, on Soulseek")} {#if key}SoulStream{/if}
{#if key} · {/if} {#if me}{me} on Soulseek{:else}not signed in to Soulseek{/if} {online ? "online" : "offline"} {#if ownLinkCurrent}linked{/if}
{#if profile} {profile.from_ledger ? "profile published" : `profile synced as of ${fmtClock(community.profileAt)}`} · rev {profile.sequence} {:else if key} nothing published yet {:else if freenetOn} {status.text} {:else} SoulStream identity off {/if} {friendCount()} friends · {slsk.buddies.length} contacts · {joinedNames.length} rooms · {convos.length} conversations
{#if hubOpen} {:else if community.section === "inbox"} {#if pendingIn().length}
{#each pendingIn() as f (f.id)}
{f.username ? display(f.username) : community.remoteProfiles[f.persona_key]?.display_name || `SoulStream ID ${fpShort(f.persona_key)}`} wants to be friends
{f.username ? `${f.username} · ` : ""}SoulStream ID {fpShort(f.persona_key)} · asked {fmtWhen(f.updated_at * 1000)}
SoulStream{f.carry === "inbox" ? "sealed · via your encrypted inbox · signed by their key" : "signed request · their ID proven over Soulseek"}·open until {fmtWhen(f.expires_at * 1000)}
friends are two signed answers — yours goes back to them over Soulseek
{/each}
{/if} {#if community.shares.length}
{#each community.shares as c (c.id)} {@const pv = community.sharePreviews[c.id]}
{c.name}
{c.entry_count} {c.entry_count === 1 ? "track" : "tracks"} · playlist · shared by {#if c.from_username}{:else}SoulStream ID {fpShort(c.from_key)}{/if} {fmtWhen(c.received_at * 1000)}
SoulStream signed by {fpShort(c.owner_key)} {#if c.carry === "inbox"}· sealed · via your encrypted inbox{/if} {#if pv && !pv.pending && !pv.error && !pv.withdrawn}·verified · revision {pv.revision_seq}{/if} {#if pv?.withdrawn}·withdrawn by the owner{/if} {#if pv?.error}·{pv.error}{/if}
tracks resolve against your library first; the rest become Soulseek streams
{/each}
{/if} {#if followNews().length}
{#each followNews() as f (f.owner_key + ":" + f.playlist_uuid)} {@const busy = !!community.busy["follow:" + f.playlist_uuid]}
{f.name}
{#if f.added.length}{f.added.length} new {f.added.length === 1 ? "track" : "tracks"} since you imported it{#if f.removed}{" · "}{f.removed} removed{/if}{:else}the owner removed {f.removed} {f.removed === 1 ? "track" : "tracks"} — your copy keeps {f.removed === 1 ? "it" : "them"}{/if} {" · "}revision {f.seen_seq} · checked {fmtWhen(f.checked_at * 1000)}
SoulStream signed by {fpShort(f.owner_key)} · your copy:
{#if f.added.length}
{f.added.slice(0, 3).map((e) => `${e.artist} — ${e.title}`).join(" · ")}{#if f.added.length > 3} · and {f.added.length - 3} more{/if}
{/if}
new tracks resolve against your library first; the rest become Soulseek streams — nothing is removed from your copy
{/each}
{/if} {#if !convos.length && !community.shares.length && !pendingIn().length && !followNews().length}
No conversations yet
Message someone from People, a room roster, or a search result. Conversations are kept on this device only — Soulseek stores nothing for you.
{:else}
{#if requests.length} {#if requestsOpen} {#each requests as c (c.u)} {/each} {/if} {/if}
{#each threads as c (c.u)} {/each}
Retained on this device only. Nothing here is a receipt — Soulseek has no delivery or read reports.
{#if slsk.activeChat && activeChat} {@const u = slsk.activeChat}
{display(u)} {#if metaOf(u).favorite}{/if} {#if metaOf(u).trusted}{/if}
{#if community.contacts[u]?.petname}{u}·{/if} Soulseek {presenceOf(u) ? presenceWord[presenceOf(u)] : isContact(u) ? "status unknown" : "not a contact — no status"}
{#if !isContact(u)} {/if}
{#each groupedMessages(activeChat.messages) as m, i (m.divider ? "d" + m.divider + i : i)} {#if m.divider}
{m.divider}
{:else}
{#if !m.me}{/if}
{m.text}
{fmtClock(m.ts)}{#if m.me} · submitted{/if}
{/if} {/each} {#if dmState.failed}
{dmState.failed}
failed ·
{/if}
{ e.preventDefault(); void sendCurrent(); }}>
{dmState.sending ? "queued locally" : "Soulseek PM · plain text"}
{/if}
{/if} {:else if community.section === "rooms"}
{#if joinedNames.length}
Joined
{#each joinedNames as n (n)} {/each} {/if} {#if favRooms.length}
Favorites
{#each favRooms as n (n)} {/each} {/if}
Directory · {fmtCount(slsk.rooms.length)} rooms
{#each directory as r (r.name)}
{fmtCount(r.users)}
{/each} {#if !slsk.rooms.length}
{online ? "The room list arrives after login — refresh if it stays empty." : "Sign in to Soulseek to see the room directory."}
{/if}
Counts are the server's. A room streams only after you join it; the global firehose stays off.
{#if slsk.activeRoom && activeRoom} {@const rn = slsk.activeRoom}
#{rn}
Soulseek Room {fmtCount(activeRoom.users.length)} here · history on this device since {fmtClock(roomSince[rn] ?? Date.now())}
{fmtClock(roomSince[rn] ?? Date.now())}You joined #{rn}. History on this device starts here.
{#each activeRoom.messages as m, i (i)}
{fmtClock(m.ts)} {m.message}
{/each}
{ e.preventDefault(); void sayCurrent(); }}>
public · everyone in the room
{#if roster.contacts.length}
Contacts here {roster.contacts.length}
{#each roster.contacts as u (u.username)} {/each} {/if}
Everyone {roster.others.length}
{#each roster.others.slice(0, 80) as u (u.username)} {/each} {#if roster.others.length > 80}
+{roster.others.length - 80} more
{/if}
{:else}
No room open
Pick one from the directory. Joined rooms keep their roster and messages here for the session.
{/if}
{:else if community.section === "people"}
{#each groups as g (g)} {/each}
{ e.preventDefault(); const k = addKey.trim(); if (k) { void friendRequestByKey(k); addKey = ""; } }}>
{ e.preventDefault(); void addContact(); }}>
{#if !slsk.buddies.length}
No contacts yet
Add someone by username, or from a room roster and a conversation. Contacts are watched for status while you are signed in.
{:else}
{#snippet personRow(p, seenCol)}
{#if p.meta.petname}{p.u}{/if} {#if p.meta.favorite}{/if} {#if p.meta.trusted}{/if} {#if friendshipFor(p.u)?.state === "friends"}{/if}
Soulseek {#if linkFor(p.u)}SoulStream{/if} {#if p.meta.group}·{p.meta.group}{/if} {#if p.st.file_count != null}·{fmtCount(p.st.file_count)} files{/if} {#if p.meta.note}·{p.meta.note}{/if}
{#if p.presence === "online"}onlineSoulseek, now {:else if p.presence === "away"}awaySoulseek, now {:else if p.st.exists === false}unknown userSoulseek has no such name {:else if fmtSeen(community.lastSeen[p.u])}{fmtSeen(community.lastSeen[p.u])}last seen by this device {:else}offlinenot seen by this device yet{/if}
{/snippet} {#if peopleOnline.length}
Online {peopleOnline.length}
{#each peopleOnline as p (p.u)}{@render personRow(p, false)}{/each} {/if} {#if peopleAway.length}
Away {peopleAway.length}
{#each peopleAway as p (p.u)}{@render personRow(p, false)}{/each} {/if} {#if peopleOff.length}
Offline or unknown {peopleOff.length}
{#each peopleOff as p (p.u)}{@render personRow(p, true)}{/each} {/if}
Contacts are yours and stay on this device. "Trusted" is a permission you granted, not a verified identity. Petnames, groups and notes live in the profile drawer — click a name.
{/if} {:else if community.section === "discover"}

Closest to you

the shelves below, scored together · every reason named
{#if ranked.length}
{#each ranked as p (p.username)} {/each}
{:else}
Nothing to rank yet. This shelf is the ones below added up — join a room, browse someone, declare a like — with contacts and friends left off.
{/if}

{slsk.activeRoom ? `In #${slsk.activeRoom} right now` : "In a room right now"}

from the room roster{#if activeRoom} · {fmtCount(activeRoom.users.length)} here{/if}
{#if roomPeople.length}
{#each roomPeople as u (u.username)} {/each}
{:else}
{activeRoom ? "Everyone in this room is already a contact." : "Join a room and the people in it show up here, with the room as their provenance."}
{#if !activeRoom}{/if}
{/if}

Recent peers

from your transfers this session
{#if recentPeers.length}
{#each recentPeers as p (p.u)} {/each}
{:else}
Nobody yet. People who send you files, or take files from you, appear here for the session.
{/if}

Similar Soulseek interests

the server's match on declared likes · not follows
{#if online} {/if} {#if community.similar?.users?.length}
{#each community.similar.users.slice(0, 24) as su (su.username)} {/each}
{:else if community.busy.similar}
Asking the server…
{:else}
{community.similar?.error ? "The server did not answer." : online ? "The server matches people on the likes you declare on Soulseek — SoulStream has none set for you yet, so this shelf is empty." : "Sign in to Soulseek to ask the server."}
{#if online}{/if}
{/if}

Library overlap

computed on this device, after an explicit browse
{#if community.overlap.length}
{#each community.overlap.slice(0, 24) as o (o.username)} {/each}

Names compared, not files: a folder counts as yours when your library has that album under that artist. Browse someone again to refresh their line.

{:else}
Nothing computed yet. Browse a person's shares and SoulStream compares them with your library locally — nothing is uploaded to do it.
{/if}
There is no global directory here and no "people you may know". Every shelf says where its people came from. Freenet discovery indexes are opt-in, curator-run, and currently off.
{:else if community.section === "spaces"}
Your Spaces {community.spaces.length}
{#each community.spaces as sp0 (sp0.id)} {/each} {#if spaceCreating}
{ e.preventDefault(); void makeSpace(); }}>
{:else} {/if}

What a Space is

A persistent SoulStream community on Freenet. The owner's signed checkpoint is the roster; a removal is a new epoch, and in a private Space every removal rotates the key. Not a Soulseek Room, and never mirrored to one. Invitations travel sealed through the encrypted inbox — the person you invite needs one. A member joins under a key minted for this Space alone, so its roster says nothing about who they are anywhere else; only the owner can put a name to one.

{#if sv}
{sv.name.slice(0, 2).toUpperCase()}
{sv.name} Space
{#if sv.private} private · sealed{:else}public{/if}· {sv.members.length} {sv.members.length === 1 ? "member" : "members"}· epoch {sv.epoch}· not a Soulseek Room {#if !sv.reachable}·not on this node yet{:else}·read from the network{/if}
{#if !spaceOwner}{/if}
{#if community.spaceError}

{community.spaceError}

{/if} {#if spaceTab === "chat"} {#if sv.pinned_uuid}

Pinned playlist {#if pinnedPreview && !pinnedPreview.pending && !pinnedPreview.error}revision {pinnedPreview.revision_seq}{/if}

{pinnedPreview?.name ?? "Reading…"}
{#if pinnedPreview?.entries}{pinnedPreview.entries.length} tracks · signed by {fpShort(sv.pinned_owner)}{:else if pinnedPreview?.error}{pinnedPreview.error}{:else if pinnedPreview?.withdrawn}withdrawn by its owner{:else}fetching through your node{/if}
{#if spaceOwner}{/if}
{:else if spaceOwner && community.published.some((p) => !p.withdrawn)}
Pin one of your public playlists:{#each community.published.filter((p) => !p.withdrawn) as p (p.playlist_id)}{/each}
{/if}
{#if !sv.messages.length}
Nothing said yet. The last {sv.message_cap} lines are kept; older ones fall away for everyone.
{/if} {#each sv.messages as m (m.id)}
{fmtClock(m.created_at * 1000)}{whoIs(m)}{#if m.text != null}{m.text}{:else} sealed to a key this device does not hold (epoch {m.epoch}){/if}
{/each}
{ e.preventDefault(); void sayInSpace(); }}>
{sv.private ? "sealed · members only" : "public · signed"} · last {sv.message_cap} kept
{:else if spaceTab === "listening"}
Share what I play in this Space

Per Space, off until you say so. A track is posted once it has actually played, never on a skip{#if sv.private}, and sealed to this room's epoch key — the same key its messages use{/if}. Leaving takes it with you.

{#if sv.share_listens}sharing{:else}not sharing{/if}
{#if nowPlaying.length}

Right now{#if nowPlaying.some((n) => n.live)}{nowPlaying.filter((n) => n.live).length} playing{/if}

{#each nowPlaying as n (n.author)}
{n.is_me ? "you" : n.username ? display(n.username) : fpShort(n.author)} {#if n.live}{:else}{ago(n.created_at)}{/if}
{n.title}
{n.artist}
{/each}

A card goes quiet after {LISTEN_LIVE_MINS} minutes with nothing new — nobody is reported as listening because the app forgot to say they stopped.

{/if}

The traillast {sv.message_cap} kept, with the chat

{#if !sv.listens.length}

Nothing played here yet. Turn the switch above on and the next track you finish appears — for everyone in the room, and nobody outside it.

{:else}
{#each trail as l (l.id)} {#if l.day}
{l.day}
{/if}
{fmtClock(l.created_at * 1000)} {(l.album || l.artist || l.title).slice(0, 2).toUpperCase()} {l.title}{l.artist} {#if l.run}{:else}{/if} {#if !l.run}{l.is_me ? "you" : l.username ? display(l.username) : fpShort(l.author)}{/if}
{/each}
{/if}
{:else if spaceTab === "holdings"}
Share what I hold in this Space

{#if sv.share_holdings && hv?.mine && !hv.mine.withdrawn}{fmtCount(hv.mine.count)} albums · published{#if sv.private} sealed to this room's epoch key{/if} · updated when your library changes · synced as of {fmtClock(hv.mine.published_at * 1000)}{:else}Album names as keys, one quality word each, and your Soulseek name so members can get it from you — never files. Per Space, off until you say so.{/if}

{#if community.busy["holdings:share"]}working…{:else if sv.share_holdings}sharing{:else}not sharing{/if}
What the room learns: members can tell whether you hold any album they can name, and roughly how many you hold. They never see files, paths, or formats beyond a lossless/lossy word. Turning this off withdraws the list; copies members already read stay with them.

Ask this room{#if hv}{hv.sharing} of {sv.members.length} sharing{/if}

{ e.preventDefault(); if (askArtist.trim() && askAlbum.trim()) asked = { artist: askArtist.trim(), album: askAlbum.trim() }; }}>
{#if asked}{#key asked.artist + "\u0000" + asked.album}
{/key}{/if}

Who shares here

{#if hv}
you {#if sv.share_holdings && hv.mine && !hv.mine.withdrawn}{fmtCount(hv.mine.count)} albums · list {hv.mine.sequence} · published {fmtClock(hv.mine.published_at * 1000)}{:else}not sharing{/if} {sv.share_holdings && hv.mine && !hv.mine.withdrawn ? "shared" : "none"}
{#each hv.members.filter((m) => !m.is_me) as m (m.key)}
{m.is_me ? "you" : m.username ? display(m.username) : fpShort(m.key)} {#if m.state === "shared"}{fmtCount(m.count)} albums · list {m.sequence}{#if m.read_at} · read {fmtClock(m.read_at * 1000)}{/if}{:else if m.state === "none"}not sharing{:else if m.state === "withdrawn"}withdrew their list{:else if m.state === "sealed"}sealed to an epoch you have no key for{:else}not read yet{/if} {m.state}
{/each}
{:else}

Reading the room's lists…

{/if}

A member's list is read when the Space opens and again after ten minutes; a member who never published costs a slow search, so they are asked again only hourly.

{:else if spaceTab === "members"}
{#each sv.members as m (m.key)}
{m.is_me ? "you" : m.username ? display(m.username) : fpShort(m.key)} {m.role}{#if m.since_epoch > 1} · since {m.since_epoch}{/if} {#if spaceOwner && !m.is_me}{/if}
{/each}
{#if spaceOwner}
{ e.preventDefault(); const k = spaceInviteKey.trim(); if (k) { void inviteToSpace(sv.owner, sv.space_id, k); spaceInviteKey = ""; } }}>

They appear in Members once they answer with the key they will wear here — an invitation on its own puts nobody on the roster.

{#if community.friends.some((f) => f.state === "friends")}

Friends: {#each community.friends.filter((f) => f.state === "friends") as f (f.id)} {/each}

{/if} {/if}

Members are their SoulStream identities here — a joined Space is a linked identity. Removing someone is a new epoch{#if sv.private}, and in this private Space it also rotates the key: those who stay receive the new one sealed{/if}.

{:else if spaceTab === "proposals"} {#if !sv.pinned_uuid}

Proposals

A board lives beside a pinned playlist: members propose adds, removes and moves against the exact revision they see, and the playlist's owner decides. {#if spaceOwner}Pin one of your public playlists from the Chat tab and the board opens here.{:else}The owner has not pinned a playlist yet.{/if}

{:else if !bv && community.boardError}

Proposals

{community.boardError}

{:else if !bv}

Reading the board through your node…

{:else}

{bv.name || "Pinned playlist"}{#if bv.withdrawn}withdrawn by its owner{:else}revision {bv.revision_seq}{/if}

Proposals bind to revision {bv.revision_seq} exactly · {bv.i_am_owner ? "you decide" : "the playlist's owner decides"} · every decision is signed · {bv.contributors.length} {bv.contributors.length === 1 ? "contributor" : "contributors"} on epoch {bv.epoch}{#if !bv.reachable} · the owner has not opened the board yet{/if}

{#if bv.can_propose && !boardDraft}{/if} {#if !bv.can_propose && !bv.i_am_owner && bv.reachable && !bv.withdrawn}You are not on this board's roster yet — the owner refreshes it from the members when they open the board.{/if}
{#if community.boardError}

{community.boardError}

{/if}
{#if boardDraft}

Your proposal {boardDraft.ops.length} / 32 changes

Move rows, take some out, add from your library. The owner receives titles, artists and albums — never a file, never a path. Nothing here plays or downloads.

    {#each boardDraft.list as e, i (e.entry_id)}
  1. {i + 1} {e.title}{e.artist}{#if e.fresh} · added{/if}
  2. {/each} {#if !boardDraft.list.length}
  3. Empty — add something below.
  4. {/if}
{#if boardHits.length}
{#each boardHits.slice(0, 8) as h (h.path)}{/each}
{/if}
{/if}

Pending {bv.proposals.length}

{#if !bv.proposals.length}

Nothing pending. {#if bv.can_propose}Propose an add, a remove or a move against the current revision — up to 32 changes each, 8 pending per person.{/if}

{/if} {#each bv.proposals as p (p.id)}
{whoProposed(p)}{fmtWhen(p.created_at * 1000)} · {p.ops.length} {p.ops.length === 1 ? "change" : "changes"}
{#if p.stale}stale · an older revision{:else}current revision{/if}
{#if p.note}

{p.note}

{/if}
    {#each p.ops as op, i (i)}
  • {op.kind === "add" ? "+" : op.kind === "remove" ? "−" : "↕"} {#if op.kind === "add"}add {op.title} — {op.artist} at #{op.position + 1} {:else if op.kind === "remove"}remove {#if op.known}{op.title} — {op.artist}{:else}an entry from an older revision{/if} {:else}move {#if op.known}{op.title}{:else}an entry from an older revision{/if} to #{op.position + 1}{/if}
  • {/each}
{#if bv.i_am_owner}
{#if !p.stale}{/if}
{/if}
{/each}

Decisions {bv.decisions.length}

{#if !bv.decisions.length}

None yet. Each decision is owner-signed with a sequence number; the last 256 are kept.

{/if} {#each bv.decisions as d (d.seq)}
#{d.seq} · {d.accept ? "accepted" : "rejected"}{d.reason}{#if d.result_hash}→ revision {fpShort(d.result_hash)}{/if}
{/each}
{/if} {:else}

About

Owner
{sv.owner}
Space id
{sv.space_id}
Mode
{sv.private ? "private — message bodies sealed to a per-epoch key" : "public — anyone who resolves the contract can read"}
Window
the last {sv.message_cap} messages
Epoch
{sv.epoch}

What the network can see: that the Space exists, its owner, its roster keys, and — for a private Space — how many sealed lines it holds. Leaving does not erase copies others hold.

{#if sv.description}

{sv.description}

{/if}
{/if}
{:else}
{community.spaces.length ? "Pick a Space" : "No Spaces yet"}
A Space is a persistent SoulStream community on Freenet with its own roster — invitation only, sealed when private, never a Soulseek Room in disguise.
{/if}
{:else if community.section === "you"}
{#if key}

Your public profile SoulStream

Anyone who has your SoulStream ID can fetch this. It is signed by your persona key and replicated — publishing is not something that can be fully undone.

{#if avatarUrl}{/if}
{#if community.busy.avatar}

Preparing your photo and publishing it — a few seconds normally; the first publication can take longer.

{:else if profile?.avatar_note && profile.avatar_note !== "none published"}

{profile.avatar_note}

{/if}

Display names are not unique. People you meet may give you a private petname; you cannot see it.

{#if community.busy.publish} pending publication {:else if community.busy.profile} reading from the network — up to a minute {:else if profile} {profile.from_ledger ? "published from this device" : `synced as of ${fmtClock(community.profileAt)}`} · revision {profile.sequence} {:else} nothing published yet {/if} {#if community.profileError}{community.profileError}{/if}
{:else}

Your SoulStream identity SoulStream

{#if !freenetOn}

A SoulStream identity is a key pair held by your local Freenet node. Turn the Freenet layer on (right) and the node will hold it; nothing is published until you say so.

{:else if !nodeUp}

Waiting for the node — {status.text}. Identity needs a connected node.

{:else}

Create a fresh key here, or bring one back from a recovery file. Creating one publishes nothing; your profile is a separate button, later.

{/if} {#if community.error}

{community.error}

{/if}
{/if}
{#if key}

Your public playlists {community.published.filter((p) => !p.withdrawn).length}

{#if !community.published.length}

Nothing published yet. Open a playlist and choose Share on SoulStream: a signed manifest goes to the network under your ID — titles, artists, albums, durations, your notes; never a file, never a path.

{:else} {#each community.published as p (p.playlist_id)}
{p.name}
{p.entry_count} tracks · revision {p.revision_seq} · {p.withdrawn ? "withdrawn" : "published"} {fmtWhen(p.published_at * 1000)}
{#if p.withdrawn} {:else} {/if}
{/each}

Withdrawing stops your pointer; copies others already fetched stay with them.

{/if}

Your SoulStream ID

The fingerprint of your persona key. It is the stable you — names change, this does not. Share it as text; nothing else identifies you across networks.

{#each fpGroups(key) as g, i (i)}{#if i}·{/if}{g}{/each}
Key version
{community.persona?.persona?.key_version ?? 1}{#if (community.persona?.persona?.key_version ?? 1) > 1} · rotated{/if}
Signs
profile today; playlists, friend links, Soulseek link and Space links as those land
Delegate
{fpShort(community.persona?.delegate_key)}

Recovery

Your identity lives in the node's key custody on this machine. Export a recovery file — encrypted with a passphrase you choose — and keep it somewhere that is not this disk.

{#if exportDone}

Written: {exportDone.path} ({exportDone.bytes} bytes). Test it on another machine before you need it.

{/if}
{#if confirmRotate} Rotating signs a transition with the old key; friends re-verify on their next fetch. {:else if confirmRemove} This deletes the key from this node. Without a recovery file, this identity ends. {:else} {/if}
{#if community.error}

{community.error}

{/if}
{/if}

Networks

Soulseek {online ? "online" : "offline"}
{#if me}Signed in as {me} · {fmtCount(network.shareFiles)} files shared.{:else}Not signed in — see Settings.{/if}
{#if !key} Create a SoulStream identity to link this account to it. {:else if !me} Sign in to Soulseek to link this account to your identity. {:else if ownLinkCurrent} Linked to this identity — proof verified {new Date(ownLink.verified_at * 1000).toLocaleString()} through a Soulseek message to yourself. Other SoulStream users who ask over Soulseek now get your signed ID; nothing is published. {:else} Not linked to your identity. Linking sends one message to yourself over Soulseek and verifies the signed answer — nothing is published, and from then on other SoulStream users who ask get your signed ID. {/if}
{#if key && me}
{#if ownLinkCurrent} {:else} {#if !nodeUp}needs the node connected{/if} {/if}
{/if}
Freenet experimental
{#if freenetOn} Local node on 127.0.0.1:{community.freenet?.ws_port ?? 7509} · {status.text} {#if statusKind(community.freenet?.status) === "node-absent"}
{#if community.nodeInfo?.binary} runs the installed peer{#if community.nodeInfo.version} ({community.nodeInfo.version}){/if} in the background · telemetry pinned off · data in the app folder {:else} No Freenet peer found on this machine — install it (cargo install freenet) and check again. {/if}
{:else if community.nodeInfo?.started_by_app}
started by SoulStream · keeps running when the app closes; this button stops it
{/if} {:else} Off. Carries your identity and profile; later playlists, friend links and Spaces. Turning it on publishes nothing. Turning it off is instant and touches nothing else in the app. {/if}
Before you rely on it — seven things that are true
  • A desktop background peer must run; it uses network, disk, memory and CPU.
  • The network is alpha. Things you publish are replicated and cannot be guaranteed erased.
  • Freenet is not an anonymity service.
  • The node's own diagnostics are a node setting SoulStream pins off; verify after upgrades.
  • Nothing is published when you enable this. Publishing is always a button.
  • Nothing here ever says "online" — a successful fetch is not presence.
  • Phones and TVs cannot run the same peer today.
{#if freenetOn} {#if key}
{#if community.inbox?.published} encrypted inbox · key {community.inbox.box_key_id} {community.inbox.last_check ? `checked ${fmtWhen(community.inbox.last_check * 1000)} · ` : ""}friend requests and shares arrive sealed to a key kept on this device; a persona restored elsewhere publishes a new one {:else} lets other SoulStream users send you friend requests and shares sealed, without Soulseek in between {/if}
{/if} {/if}

Devices later

A paired device gets its own key and a signed grant — it never receives your root key or Soulseek password. Comes after the profile work settles.

{/if}
{#if drawerUser}
{/if}