Fixes a crash that blanked the Support (About/donate) view with React
error #130 on every release build since v0.13.2. react-qr-code ships
CommonJS, and since the Vite 8 / Rolldown migration its default import
resolved to the module namespace object ({ QRCode, default }) instead of
the component, making <QRCode/> an invalid element. AboutView now unwraps
the import to the real component. Reproduced against the production bundle
and verified fixed the same way.
Also ships the configurable HTTP/SOCKS5 network proxy (incl. Tor) from
PR #10 by Anderseta, merged earlier: routes relay WebSockets, Rust-side
fetches and update checks through the proxy. The proxy settings and the
README/CHANGELOG note that DNS is not yet guaranteed private (issue #11).
Adds a Contributors section to the README (Anderseta, SondreB).
Adds a Settings UI to route Vega through an HTTP or SOCKS5 proxy (e.g. Tor).
Covers both network stacks: the webview (proxy_url applied at build time, so
relay WebSocket traffic is proxied) and all five Rust-side plugin-http call
sites (converted to fetchWithProxy). The auto-updater's check and download are
also routed via check({ proxy }), so update traffic no longer bypasses the
proxy and leaks the user's IP.
socks5h:// is intentionally rejected for now: Tauri's webview proxy API only
documents http and socks5, so accepting socks5h globally would imply a DNS-leak
guarantee the webview can't yet make. Tracking a cross-platform DNS-leak test
as follow-up.
By Anderseta.
The app identifier changes from `com.hoornet.vega` to `com.veganostr.Vega`.
A reverse-DNS app ID must sit on a domain the project controls; the old one
did not, which blocks publishing on Flathub and leaves Vega's identity
inconsistent across Flathub, winget and the native installers.
The identifier keys every per-app directory, so this migrates existing
installs on first launch: the SQLite cache (vega.db), the embedded relay's
database (relay.db), and webview localStorage (themes, drafts, podcast
subscriptions, article read-state).
Migration runs at the top of run(), before tauri::Builder — not inside
.setup(). Tauri builds the config-defined windows before invoking the setup
hook, and building a webview create_dir_all's <LocalData>/<identifier>, which
on Linux is the same directory that holds vega.db. Migrating from setup()
would therefore always find a non-empty destination, skip, and silently
strand every Linux user's data with no way for a later fix to recover it.
Entries are moved individually rather than renaming the whole tree, so a
destination the webview already created does not block the migration, and a
failed attempt retries on the next launch. An entry that already exists at
the destination is never overwritten — the live copy always wins.
All three per-platform roots are covered: WebView2 keeps localStorage under
%LOCALAPPDATA% on Windows (not %APPDATA%), and WKWebView keeps it under
~/Library/WebKit on macOS, neither of which is the app data dir.
Keys are unaffected — the keychain service name is independent of the
identifier, so users stay logged in.
Windows installs v0.14.0 alongside v0.13.2 rather than replacing it, since
the changed identifier reads as a new application. Documented in the README,
CHANGELOG and release notes.
- CHANGELOG: add v0.13.2 entry (note re: v0.12.10–v0.13.1 in GitHub Releases)
- README: mute-aware search, resizable sidebar, collapsing compose box, update-banner changelog; bump verify example to 0.13.2
- CLAUDE.md: add the four v0.13.2 features to the Implemented list
- Search respects the mute list (#7)
- Compose box collapses to a single line when idle (#6)
- Resizable left sidebar (#6)
- Update banner shows the new version's changelog
- Build: Vite 8 migration, dropped vulnerable esbuild dev dependency
The updater already fetched the release notes (update.body) but never
displayed them. The banner now has a 'What's new' toggle that expands a
panel showing only the newest version's section (sliced out of the
cumulative release body), rendered as markdown, with a 'Full changelog
on GitHub' link to the releases page.
Adds a drag handle on the right edge of the expanded sidebar; width is
clamped to 160-360px, double-click resets to the default, and the chosen
width persists to localStorage. Collapsed state is unchanged.
Closes#6
The posting area was always full-height, which felt oversized (reported
on macOS). It now shows a single line when empty and unfocused, and
expands to full height + toolbar on focus or when it has content. Uses
focus-within blur semantics so clicking the emoji/attach/poll/post
controls doesn't collapse it mid-interaction, and resets the auto-resize
inline height on collapse. A saved draft starts expanded.
Part of #6
Search only filtered the people *suggestions* list — actual note,
article, and people results ignored the mute-list entirely, so muted
spam accounts still showed up. Now notes/articles are filtered by muted
pubkey and muted keyword, and people by muted pubkey, reusing the same
logic the feed uses. Tab counts, zero-result states, and all result
lists use the filtered sets and react to mute changes.
Closes#7
Vite 8 switches the bundler to Rolldown, which no longer accepts the
object form of rollupOptions.output.manualChunks — it must be a function.
Convert it (same ndk/vendor split intent) and give tsconfig.node.json a
modern target/lib so vite.config.ts type-checks against String.includes.
WebKitGTK collapses body <img> elements to 0 height during re-decode,
shrinking scrollHeight and clamping scrollTop — articles with body images
locked partway down. Give every .prose-article img a fixed 16:9
aspect-ratio + object-fit: contain so the layout box survives the collapse
and scrollHeight stays constant. Body images now click-to-open the existing
ImageLightbox (with cursor: zoom-in affordance), and the lightbox image
itself now closes on click instead of swallowing it.
- Themes: 7 -> 8, add the new Reader theme.
- Add Easy-Read Font (Atkinson Hyperlegible) to Personalization.
- Add 'view your secret key in Settings' to Identity.
- Drop the false 'Live streaming feed — no manual refresh needed' claim:
the live subscription has been disabled since v0.12.8. Replaced with an
accurate virtualized-feed / infinite-scroll description. (Auto-refresh
returns in v0.13.1 — README feed line to be revisited then.)
- Bump the minisign verify example to 0.13.0.
- useUpdater: guard the install_info response — a null/garbage value no
longer overwrites the optimistic default and crash the whole app on
installInfo.can_self_update. (Real app never returns null here; this is
defensive — a failed updater check must not take down Vega.)
- tauri-dev-mock: add an install_info case so browser dev mode
(localhost:1420) doesn't crash on the post-onboarding render.
- onboarding: the interests step claimed "you can always change this
later" — there is no interests editor anywhere. Copy is now honest
about what interests do (surface as hashtag pills on an empty
Following feed).
- Onboarding Shell: larger accent VEGA wordmark + step-progress dots
across the create -> backup -> interests path.
- Backup step gains an "I'll back up my key later" escape hatch. It runs
the SAME keychain-saving login as confirming (skips only the checkbox)
— wiring it to onComplete alone would have lost the generated key.
- Settings -> Identity now reveals the secret key (nsec): reveal/hide/
copy via the existing load_nsec keychain command, hidden for read-only
and remote-signer accounts. This makes the escape hatch honest — its
note truthfully points users to where they can retrieve the key, and
fills a real gap (the nsec was previously unreachable after onboarding).
The Unicode/symbol icons (◈ ☰ ▶ ⌕ ⟐ ✦ …) rendered inconsistently across
platforms and sizes. Lucide is crisp and pixel-perfect at any zoom.
- Add lucide-react (pinned exact); tree-shakes to the 15 icons used.
- NAV_ITEMS is now NavItem[] with ReactNode icons (the as-const array
couldn't carry JSX). View type exported from the ui store for it.
- Icon spans switched from text-centred glyphs to flex-centred SVGs.
- Write-article ✦ -> PenLine.
New Settings → Appearance toggle, "Easy-Read Font". When on, swaps the
UI sans-serif to Atkinson Hyperlegible — a typeface designed by the
Braille Institute for legibility (distinct b/d/p/q, clear 1/I/l). Helps
dyslexic readers and anyone in a long reading session.
- Bundled via @fontsource/atkinson-hyperlegible (400 + 700), pinned exact
— no runtime font fetch, works offline.
- Toggle adds an html.font-readable class; CSS overrides --font-ui and
nudges letter-spacing (+0.012em) and line-height (1.6 -> 1.65) per
evidence-based dyslexia guidance.
- Persisted to localStorage (wrystr_easy_read_font), applied on <html>
at startup. Independent of theme — pairs with any, Reader included.
- Code blocks keep font-mono; articles keep their reading serif.
Follow-up to the Reader theme: colour was ~60-70% of the dyslexia-
friendly benefit, the font is most of the rest.
- Sepia: refined warm-dark elevation ladder (was muddy), proper text
hierarchy (text-muted/text-dim were near-identical), considered amber
accent. The reading-by-candlelight feel kept and sharpened.
- Hackerman: deeper phosphor base. Fixed a real bug — accent was the
same hex as primary text (#00ff41), so the accent disappeared into
the text. Accent is now a lighter phosphor green, distinguishable.
Acid #ffff00 zap softened to a less-painful warm yellow.
- Reader (new, 8th theme): cream + dark-warm-grey + saddle-brown accent.
Bookish, low-glare, moderate contrast. Helps dyslexic readers (per
evidence-based guidance: off-white base, no pure-black text, calmer
contrast) and anyone reading long-form articles. Borrows from print
book design rather than screen design. A dyslexia-friendly font is
a worthwhile follow-up but lives outside the colour-only theme system.
- Base shifts from #f5f5f5 to #fbfbfc (softer off-white, faint cool tint)
— pure-white-card-on-soft-base elevation reads instead of being flat.
- bg-hover #f0f1f4 (darker on hover, the light-theme inverse).
- text-muted vs text-dim were near-identical (#697180 vs #6c717a) — no
real hierarchy. Now #5b5f6e (WCAG AA on bg) and #888c98 for hints.
- Deeper violet accent #6d28d9 — carries more weight on white than the
previous shade did.
- Semantic colours retuned for light backgrounds (zap, danger, warning,
success all use the deeper Tailwind-700 family for proper presence).
- Base shifts from pure #0a0a0a to a barely-cool #0d0e12 ("midnight sky"
reads as deep blue-black, not generic OLED black).
- Even elevation ladder: bg -> bg-raised -> bg-hover now reads as clear,
perceptible steps instead of muddy near-duplicates.
- Three-tier text hierarchy with verified contrast: primary near-white,
muted (WCAG AA on bg), dim for timestamps/hints.
- More luminous violet accent (#a78bfa) with proper pressed variant.
- Semantic colours retuned to harmonise (zap warm gold, success fresh
green, danger softer red).
Mirrored into index.css @theme so pre-applyTheme initial paint matches.
Two remaining causes of upward-scroll flicker in the virtualized feed:
- QuotePreview rendered nothing until fetchNoteById resolved, then popped
in a box — a 70-200px late growth that desynced the virtualizer every
time a quote note mounted. It now renders a fixed 80px (h-20) box,
reserved whether or not the quoted note has loaded; a 2-line clamp keeps
resolved content inside it.
- estimateSize was a flat 140px, so every row snapped from 140 to its real
height (96-1660px) on first measurement. New estimateNoteHeight predicts
height from note content (text, media boxes, quote box) so the
first-measure correction is small enough to be imperceptible.
Remaining: a <=25px transient on some notes' first few views (a flex-wrap
row reflowing as async content lands); self-heals as caches warm.
Three compounding causes in the v0.12.17 virtualized feed:
- Measurement cache was keyed by list index; the feed reorders (refresh
merge, WoT filter, tab switch, cached->fresh swap) so cached heights
reattached to the wrong note. Fixed with getItemKey by note id.
- <video> and single <img> had no reserved height and resized after
metadata/image load — after the virtualizer measured the row. They
now sit in fixed aspect-ratio boxes.
- The custom measureElement returned the 140px estimate for not-yet-
measured rows on backward scroll (e.g. notes revealed by toggling
WoT), so tall cards rendered far bigger than their recorded size.
Removed — stable box heights make the flicker workaround obsolete.
Media boxes always render (stable height); the <img>/<video> inside
mounts only when the card is on screen, keeping scroll light.
Adds older-notes pagination to the Global feed. fetchGlobalFeed gains an
optional until param (fetch events before a timestamp, no since bound).
The feed store's new loadOlderNotes action fetches 50 older notes when
triggered, dedups, merges, re-sorts. MAX_FEED_SIZE raised 200 to 1000 —
virtualization bounds the DOM-node/bitmap count, so this just caps the JS
notes array as hygiene.
Feed.tsx auto-triggers loadOlderNotes when the user scrolls within ~8 rows
of the bottom; a 'Loading older notes' row shows during the fetch. Global
tab only (Following uses a separate path, Trending is a ranked snapshot).
Pending: sandboxed memory verification.
Stage 1 of the feed virtualization work. The feed now renders only the
visible window of note cards (~25-35 in the DOM) instead of all up to 200,
via @tanstack/react-virtual (pinned 3.13.24). This structurally caps the
WebKit decoded-bitmap accumulation that caused the v0.12.6-era OOM.
Error and 'new notes' banners moved above the scroll container so the
virtualizer's coordinate space always starts at scrollTop 0. Upward-scroll
flicker fixed with a scroll-direction-aware measureElement that reuses the
cached row height on backward scroll (TanStack/virtual#659).
Feed still capped at MAX_FEED_SIZE; infinite scroll is Stage 2.
The app now behaves coherently for users without a signer (fully logged
out, or signed in with an npub). No broken Publish buttons, no dead-end
"Not logged in" toasts.
- Add useCanSign() hook in src/stores/user.ts as the single source of
truth for write-capability. Captures both "no pubkey" and
"npub-only login" states.
- ReadOnlyBanner now fires for both states (was only "no pubkey" before).
- Hide account-bound sidebar entries (Bookmarks, Messages, Notifications,
Zaps, V4V) in read-only mode. Read-only-OK views (Feed, Articles, Media,
Podcasts, Search, Follows, Relays, Settings, Support) stay visible.
- Guard every write surface, two patterns:
- Hide inline UI: ComposeBox, InlineReplyBox, NoteActions row, NoteCard
context menu, ArticleFeed "Write article", FollowsView per-row
follow/unfollow, ThreadView root reply, PollWidget vote controls,
RelaysView "Publish list", PodcastPlayerBar ShareButton.
- "Sign in to X" CTA: ArticleEditor publish, QuoteModal post, ZapModal,
EditProfileForm save. CTAs open LoginModal.
- ProfileView edit/follow/mute/zap/DM buttons each gated by canSign.
- ArticleView like/repost/comment/bookmark/zap each gated by canSign.
- Belt-and-suspenders runtime guards in user store follow/unfollow.
Bookmark / mute stores already swallow publish errors via .catch(() => {}),
so they don't need explicit runtime guards.
CI tauri-action enforces matching major/minor between the @tauri-apps/api
JS package and the Rust tauri crate. v0.12.10's first build failed because
the JS side was still at 2.10.1 after only the Rust crate was bumped.
- Website was never linked from the README. Add a header link line
(Website / Download / AUR) directly under the tagline and a second
pointer in the Download section.
- Verify example used vega_0.12.1_amd64.AppImage.tar.gz — AppImage was
dropped in v0.1.6. Swap in a current .deb example.
- Sig note was stale: .deb and .rpm actually ship with minisign
signatures now (tauri-action unified installer and updater artifacts).
Only .dmg is unsigned.
- Web of Trust wasn't mentioned in the features list despite shipping
in v0.11.0 and being extended to reactions/zaps/all tabs in v0.12.9.
Added under Feed & content next to keyword muting.
- Drop "custom Go relay" from Up Next — Vega Relay is live and is
Vega's default relay. Swap in real remaining work (NIP-96, WoT-
powered feed ranking).
- Add NIP-32 row — partial support via the language filter's script
tags.
Extends the WoT filter beyond the global feed: reaction pills, zap
totals, and all feed tabs (global, following, trending) now respect
the trust graph. Also drops the "new account" badge, since the
kind-0 created_at proxy was unreliable.
Softens the v0.12.8 forward-reference to Blossom — it will reappear
in a future release, not specifically this one, since it still needs
a safe-probe or allowlist (see WEBKIT_OOM_INVESTIGATION).
The badge marked notes from pubkeys whose kind-0 profile event was
newer than 60 days, on the theory that that approximated account age.
It doesn't: kind-0 created_at is "profile last updated," so any user
who refreshed their bio recently got flagged as new regardless of how
long they've been on Nostr. The proxy was misleading, so drop it
entirely until there's a real signal to use.
- NoteCard.tsx: remove isNewAccount badge.
- social.ts: remove batchFetchProfileAges, getProfileAge, and the
module-level profileAgeCache Map.
- nostr/index.ts: drop the barrel re-exports.
WoT was previously global-feed only. Now it filters notes on every feed
tab (global, following, trending) and also gates reaction pills and zap
totals — so a reaction from someone outside your social graph no longer
shows up in the counts.
- engagement.ts: wotSet param threaded through groupReactions,
fetchReactions, fetchZapCount, fetchBatchEngagement. Zaps are filtered
by the pubkey inside the zap request (the actual zapper), not the
outer event.pubkey (the LNURL wallet). Extracted getZapperPubkey and
getZapAmountSats.
- useReactions / useZapCount: cache key embeds WoT state so filtered
and unfiltered counts don't collide. Hooks subscribe to the WoT store
so toggling re-renders.
- feed store: reads WoT state and passes wotSet to fetchBatchEngagement,
seeds cache with the correct key.
- Feed.tsx: drop the tab === "global" guard.
- SettingsView.tsx: update copy to reflect the wider scope.
scripts/memory-test.sh records peak/min WebKit RSS per user-driven
phase (login, global scroll, following scroll, thread open, etc.)
and writes a TSV for side-by-side comparison between builds. Used
during the v0.12.8 OOM investigation and kept for future
regressions. Output files gitignored.