From-source Flatpak build for com.veganostr.Vega on GNOME 50 runtime.
Screenshots and the manifest git-pin are finalized before the Flathub PR.
Vendored cargo/node sources are generated (gitignored; live in the flathub repo).
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).