23 Commits

Author SHA1 Message Date
enki a2f6da814b Ignore release artifacts and OS/editor junk
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:35:11 -07:00
enki a72c20badb Mark 0.1.0 as the initial public release
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki d3e427c4c9 Add an optional NIP-89 client tag with a per-compose toggle
Notes, replies, quotes, polls and articles can carry ["client", "Sunstone"]
so other clients show what you posted with. NIP-89 says this is opt-out, so
it is a persisted setting (default on) exposed as a "Sunstone" toggle in both
the note composer and the article editor — toggling either sets your default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 31e12f232b Make the catch-up indicator a themed pill tied to real sync
Replace the square banner with a rounded, translucent "Catching up…" pill
(bottom-centre, clear of the new-notes pill) with a spinner, matching the
new-pill styling. Drive it by actual network activity: each merge bumps a
timestamp and a 1s ticker hides the pill ~4s after merges go quiet (60s hard
cap), so it runs as long as the sync actually does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 36d23214f3 Show a "catching up" banner during startup sync
Cached notes paint instantly but the relay catch-up keeps running underneath,
so a half-full feed at launch read as broken. Add an adw::Banner under the feed
header that says it's updating in the background, auto-dismissed after the
initial sync window. Init-only — no new messages or handler changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki eec9584126 Budget the texture cache by bytes, not count, to stop decode thrash
The count cap (200) was below the feed working set, so cards re-decoded on
every rebuild and saturated the shared decode gate — starving avatars (which
decode through the same gate) so disk-cached profile pictures never painted,
and everything got slow. Switch to a byte budget with headroom (feed 384 MiB,
video/streams 192, light pages 96) so normal browsing never evicts and only a
long session trims the oldest entries. Memory stays bounded without the thrash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 4a6cb3ffed Give the zap dialog a visible close button
The zap picker was a bare adw::Dialog with no header — the only way out was
Escape, which isn't discoverable. Wrap its content in a ToolbarView +
HeaderBar so it gets the standard close (✕) and title, matching the composer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 5631f3aab5 Sync stream volume to the slider on start
The volume slider inits to full, but its value-changed handler is wired after
that initial set, so playbin never heard about it — the bar showed full while
playback sat at playbin's own default, and you had to wiggle the bar to sync.
Set playbin volume to 1.0 at construction and re-assert the slider value on
each play() (playbin3 can drop volume across a Null→Playing cycle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 15629ec5a9 Cap per-page texture caches to stop unbounded RSS growth
Every page held an uncapped HashMap<String, gdk::Texture> that only ever grew
— gdk textures keep their decoded RGBA resident, so every image/avatar/cover
scrolled past stayed in memory for the session (the multi-GB RSS climb). Add a
bounded TextureCache (insertion-order eviction past 200 textures; the core
disk/byte cache still holds the source for cheap re-decode) and use it on all
seven pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 4f2a3443d4 Fix cargo-deb asset paths and maintainer email
Asset source paths are resolved relative to the crate dir, not the workspace
root, so the desktop/icon/README paths were unresolvable. Point them at the
crate (../../ for workspace-root files). Maintainer email → enki@sovbit.host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 3a090250b6 README: cut marketing tone and false/overstated claims
- Plain description, no pitch framing.
- Remove highlights (NIP-84) from features — not built; moved to Roadmap.
- DMs / proxy stated plainly, no caveat essays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:33:58 -07:00
enki 5557c4022d Show reposts on the profile (NIP-18)
The profile only fetched the author's own kind-1/long-form notes, so their
reposts never appeared. Also pull their kind 6/16 reposts, resolve each
boosted note (embedded in the repost content, or fetched by e-tag), and merge
them into the timeline ordered by repost time. Cards carry a reposted_by
marker and render a "🔁 Reposted" header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki 12d386b979 Count the whole thread in a note's comment tally
The reply count only credited a reply to its immediate parent, so a note
showed 2 when a back-and-forth under those comments really meant 7+. Per
NIP-10 a nested reply still e-tags the thread root, so count a reply toward
every batched note it references (deduped per event) — the count now reflects
the whole conversation, not just top-level replies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki 577082d9ce Drop the wallet transaction list, keep balance + alias
list_transactions isn't reliably available over the connected wallet, so the
TX list was dead weight. Keep the balance + node alias (the useful, reliable
"wallet info"); wallet_overview now only calls get_info + get_balance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki c6b1f50eee Fetch wallet transactions in spec-compliant pages (≤20)
NIP-47 caps list_transactions pages small; a 50-item request blows past the
64KB payload many relays reject at, which surfaced as an empty TX list. Drop
to 20, and log the failure at warn so an empty list is diagnosable (method
unsupported vs relay timeout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki 46d1eead69 Fix dm_probe example for the Arc<Core> conversations receiver
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki 59761a4d4f Show wallet balance and transactions on the wallet page
New Core::wallet_overview queries the connected NWC wallet for node alias,
balance, and recent transactions (get_info / get_balance / list_transactions,
all concurrent and individually best-effort). The Wallet settings tab gains a
"Balance & activity" section: balance headline, a refresh button, and a
transaction list (direction-tinted amounts, descriptions, relative times). It
loads on open when a wallet is configured and after saving a new NWC URI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki 1d87737a2e Speed up DM loading by decrypting gift wraps in parallel
dm_cache is per-session, so the first time Messages opened after each launch
re-decrypted the whole mailbox one gift wrap at a time on a single thread —
the real cause of slow DM loads. Decrypt the wraps concurrently across the
runtime's worker threads instead (unwrap_dm stays cache-backed, so warm
wraps still return instantly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki 45851a4361 More feedback on posting, likes, reposts, deletes
- Posting no longer claims success when zero relays accepted the event:
  note/reply/quote/poll now error ("saved locally — try Broadcast") so the
  composer surfaces it instead of a false "posted".
- The post button shows a spinner while publishing.
- Like / react / repost / delete were silent on failure; they now toast the
  error, and repost/delete confirm success ("Reposted ✓", "Note deleted").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki ef2f2ab115 Show reply/comment count on note cards
The reply button now carries a live count next to it, alongside the like,
repost, react and zap tallies. engagement_for counts direct replies (kind-1
events whose reply target is the note) so the number rides the same fetch +
optimistic-update path as the rest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 12:32:36 -07:00
enki a210fde559 Rewrite README for nostr users, not nostr devs
Restructure as a friendly landing page: a plain one-liner, a "what you
can do" section in human language, then install — with the dense NIP
list, build-from-source, env vars, and project layout moved to a
"technically inclined" section at the end.

Accuracy fixes:
- Flatpak install is now download-the-prebuilt-bundle from Releases
  plus the one-time Flathub remote, not the flatpak-builder dev flow.
- Arch install points at the AUR (paru/yay) instead of `makepkg -si`,
  which fails because no v0.1.0 tag exists.
- Drop the `cargo deb` quick-install (no packaging metadata yet); note
  a .deb is in progress and point to Flatpak/source.
- URLs point at git.utn.lol/enki/sunstone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 00:12:19 -07:00
enki af08e5ced2 Add local Arch package build helper
- packaging/arch/build-local.sh: build a native .pkg.tar.zst from the
  current git HEAD without needing a published forge release (tarballs
  the checkout and runs makepkg --nodeps with the existing PKGBUILD).
- .gitignore: ignore dist/ (local package output).
- sunstone-gtk: drop invalid `margin: 0 auto` on .kind-badge children;
  GTK CSS rejects `auto` for margin, which logged a parse warning. The
  value was already being ignored, so rendering is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:58:13 -07:00
enki 4c88003175 Sunstone 0.1.0 — native Linux nostr client
A GTK4 + libadwaita nostr client in Rust on rust-nostr: social feed with the
outbox/gossip model, long-form articles, live streams and video, NIP-17 DMs,
NIP-29 groups, calendar, polls, and NIP-57 zaps over NWC.

Published under the MIT license.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:06:30 -07:00
3 changed files with 25 additions and 48 deletions
+2 -2
View File
@@ -18,11 +18,11 @@ Sunstone.zip
# Packaging build artifacts
/dist/
/packaging/arch/*.pkg.tar.zst
/packaging/arch/src/
/packaging/arch/pkg/
# Prebuilt packages — distributed via the Releases page, never committed
*.pkg.tar.zst
# Release artifacts (anywhere in the tree)
*.deb
*.flatpak
+15 -23
View File
@@ -1,31 +1,26 @@
<p align="center">
<img src="crates/sunstone-gtk/assets/sunstone-mark.svg" alt="Sunstone" width="120">
</p>
# Sunstone
A native Linux desktop [nostr](https://nostr.com) client — GTK4 + libadwaita,
written in Rust on [rust-nostr](https://github.com/rust-nostr/nostr).
Follows feed, long-form articles, live streams, video, DMs, groups, calendar,
and zaps. Outbox (gossip) model on by default.
> Status: Im still adding things as I see fit. Most things should be stable. There will be bugs and Ill try not to break shit with updates.
> I want add music notes and expand on that side of things.
Social feed, long-form articles, live streams, video, DMs, groups, calendar,
and zaps. Outbox (gossip) model on by default. Feeds are chronological.
> Status: usable daily-driver, pre-1.0. Things move fast and may break.
## Features
**Login**
**Identity & keys**
- Local `nsec`, stored NIP-49 encrypted (`ncryptsec`) behind a passphrase
- NIP-46 remote signer (bunker) support
- NIP-46 remote signer (bunker) support
- NIP-05 verification
**Feed & posting**
- Following feed + browse any relay; NIP-65 relay lists with gossip/outbox routing
- Notes, replies and threads.
- Notes, replies and threads (NIP-01/10) with inline `@`-mention typeahead
- Quote reposts and reposts (NIP-18); reactions incl. NIP-30 custom emoji (NIP-25)
- Polls (NIP-88)
- Content warnings (NIP-36), deletions (NIP-09).
- Polls (NIP-88), highlights (NIP-84)
- Content warnings (NIP-36), deletions (NIP-09), comments (NIP-22)
- Live note preview, broadcast to relays, seen-on-relays, and copy raw JSON
**Articles** — long-form (NIP-23): discovery (Following / second-degree Network /
@@ -35,6 +30,7 @@ with system spellcheck, and your published + draft library.
**Media & live**
- Live streams with HLS playback + NIP-53 chat; adaptive quality, audio-only mode
- Video (NIP-71); inline feed video; picture-in-picture
- Media upload via NIP-96 / Blossom, NIP-92 `imeta`
**Zaps** — NIP-57 lightning zaps over a NIP-47 (NWC) wallet.
@@ -45,13 +41,13 @@ with system spellcheck, and your published + draft library.
**Organize** — NIP-51 bookmarks, NIP-37 encrypted drafts, time-sectioned and
grouped notifications (replies / mentions / reactions / reposts / zaps / poll votes).
**Proxy** — relay and media traffic can be routed through a SOCKS5/Tor proxy. I plan on adding internal TOR and I2P support soon.
**Proxy** — relay and media traffic can be routed through a SOCKS5/Tor proxy.
## Install
Download a prebuilt package for your distro from the
[Releases page](https://git.utn.lol/enki/sunstone/releases), then install the
downloaded file — or build from source.
There are no distro repositories yet. Prebuilt packages are built by hand and
attached to the [Releases page](https://git.utn.lol/enki/sunstone/releases)
download one, or build from source.
### Flatpak (any distro)
@@ -71,11 +67,7 @@ sudo apt install ./sunstone_0.1.0-1_amd64.deb
### Arch Linux
```sh
sudo pacman -U sunstone-0.1.0-1-x86_64.pkg.tar.zst
```
Or build it yourself:
No AUR package yet — build it from the bundled recipe:
```sh
cd packaging/arch
+8 -23
View File
@@ -1,11 +1,7 @@
#!/usr/bin/env bash
# Build a native Arch package (.pkg.tar.zst) from the CURRENT WORKING TREE
# (the files on disk right now), without needing a published forge release.
#
# This repo is synced across machines with Syncthing, so the on-disk tree —
# not git HEAD — is the source of truth. We snapshot the working directory
# (minus VCS metadata and heavy build/output dirs) and run makepkg on it, so
# uncommitted/unpushed changes are included.
# Build a native Arch package (.pkg.tar.zst) from the current git HEAD, without
# needing a published forge release. Uses the same recipe as ./PKGBUILD but
# sources a tarball of the local checkout instead of downloading from the forge.
#
# Usage: packaging/arch/build-local.sh
# Output: dist/sunstone-<ver>-<rel>-x86_64.pkg.tar.zst (in the repo root)
@@ -13,7 +9,7 @@
# Install the result with: sudo pacman -U dist/sunstone-*.pkg.tar.zst
set -euo pipefail
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
repo_root="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)"
pkgbuild="$repo_root/packaging/arch/PKGBUILD"
ver="$(awk -F\" '/^version = / {print $2; exit}' "$repo_root/Cargo.toml")"
@@ -21,22 +17,11 @@ distdir="$repo_root/dist"
workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT
echo ">> Packaging sunstone $ver from the working tree at $repo_root"
echo ">> Packaging sunstone $ver from $(git -C "$repo_root" rev-parse --short HEAD)"
# Snapshot the working tree under sunstone/ (matches _srcdir in PKGBUILD),
# excluding VCS metadata and heavy build/output dirs we don't want compiled in.
tar czf "$workdir/sunstone-$ver.tar.gz" \
--transform 's,^\./,sunstone/,' \
--exclude='./.git' \
--exclude='./.claude' \
--exclude='./target' \
--exclude='./build-dir' \
--exclude='./.flatpak-builder' \
--exclude='./dist' \
--exclude='./design_handoff_*' \
--exclude='./*.flatpak' \
--exclude='./Sunstone.zip' \
-C "$repo_root" .
# tracked files only, extracted under sunstone/ to match _srcdir in PKGBUILD.
git -C "$repo_root" archive --format=tar.gz --prefix="sunstone/" \
-o "$workdir/sunstone-$ver.tar.gz" HEAD
# Local PKGBUILD: pin pkgver + swap the forge source for the local tarball.
sed -e "s|^pkgver=.*|pkgver=$ver|" \