Commit Graph
100 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 4.8 0ae69a2774 Board: show only real VoxVera flyers, not foreign kind-30078 events
kind 30078 is a shared "application data" kind, so other apps (e.g.
artstr.studio) publish under it too — and relays may ignore our #t
filter, or an unrelated note may carry a "voxvera" hashtag. The board
only checked kind + t-tag, never the payload, so foreign events could
appear.

parseFlyers now also requires VoxVera's own markers: a `voxvera:` d-tag
and a `type: "voxvera_flyer"` content payload (both real flyers and
tombstones have these), filtering foreign events out client-side
regardless of relay behavior.

Test: a foreign kind-30078 event carrying a voxvera t-tag is excluded
while a real flyer shows. Full suite 384 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 22:37:57 -04:00
thePR0M3TH3ANandClaude Opus 4.8 6fe9069f82 Add Open Graph preview image (1200x630 card) site-wide
Shared voxvera.org links now render a real image card, not just a text
summary. Adds site/og-card.jpg (1200x630, ~90KB, derived from the
"say something… voxvera.org" custom art) and wires og:image +
twitter:image + image dimensions/alt on the home, board, and safety
pages, bumping twitter:card to summary_large_image.

Per-flyer previews remain out of scope (fragment URLs are invisible to
servers; this stays a purely static site). Test asserts all three pages
declare the og:image and that the asset is served as JPEG. Full suite
378 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 22:27:03 -04:00
thePR0M3TH3ANandClaude Opus 4.8 18fa382632 Add NIP-46 nostrconnect (QR) login path; move board safety link to footer
NIP-46 now supports both connect directions on the editor and the board:
- bunker:// (existing): paste a link the signer app generated.
- nostrconnect:// (new): the client generates the link, renders it as a
  QR (+ a copyable link for one-device use), listens on a rendezvous
  relay (relay.nsec.app), and the signer app scans it and connects back —
  proven by echoing the client's random secret. The discovered signer
  pubkey then drives the durable connection (shared adopt path).

Both paths stay session-only with an ephemeral local key; the QR uses
the same hand-rolled NIP-44 client (NIP-04 decrypt fallback). qrcode-
generator is now loaded on the board too. 3 new localized strings
(scan / waiting / copy-link) across all 14 languages in NOSTR_UI and
BOARD_UI; i18n parity passes.

Also per feedback: the board's "Safety & privacy" link moves out of the
topbar (where it ran together with "Back to VoxVera") into a centered
footer link matching the home page, and both pages now link /safety.html
(works on the static host and Vercel alike).

Tests: editor and board each connect via a fake signer that "scans" the
nostrconnect QR (reads the on-screen URI, echoes the secret) — all 6
engines. Full suite 372 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 21:49:37 -04:00
thePR0M3TH3ANandClaude Opus 4.8 b38dea1294 Add NIP-46 remote signer to the board login gate
The board's connect gate now offers "Remote signer (NIP-46)" alongside
NIP-07 / nsec / create-key. Pasting a bunker:// link establishes the
viewer's pubkey (for the web-of-trust filter and display) and keeps the
connection alive so getBoardSigner can sign reports and deletions
remotely this session — its whole point being that the secret never
leaves the signer app. After a reload the board reconnects read-only from
the remembered pubkey (session-only, like the editor).

The minimal NIP-46 client (parse bunker URI, NIP-44 codec with NIP-04
decrypt fallback, raw-WebSocket request/response) is ported into board.js,
which is an independent IIFE. Reuses the editor's translations for the 3
new BOARD_UI strings across all 14 languages (i18n parity passes).

Test: connect the board via a fake bunker signer doing real NIP-44
crypto, confirm it reveals as that identity, and that a report is signed
through the remote signer (kind-1984 with the signer's pubkey). All 6
engines. Full suite 360 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 15:04:12 -04:00
thePR0M3TH3ANandClaude Opus 4.8 29ed45533d Add localized Safety & privacy page + static link-preview meta
New standalone /safety page (safety.html / safety.js), localized across
all 14 languages, in plain language: what VoxVera protects, what it does
NOT (relays see your IP; the anonymous key is stored unencrypted; a short
PIN won't stop a targeted attacker; published events are public and
permanent; nsec-paste risk; reports are public; the host/relays still see
requests), and operational tips (Tor/VPN, disposable keys, signer apps).
Linked from the editor and the board. Aimed at the activist audience the
aesthetic invites — being honest about limits matters more than features.

Also adds static site-wide Open Graph / Twitter-card meta to the home,
board, and safety pages so a shared voxvera.org link renders a real
title/description instead of nothing. Per-flyer previews remain a
deliberate non-goal: the naddr lives in the URL fragment (invisible to
servers) and per-flyer meta would require server-side rendering, which is
out of scope for a purely static site.

i18n guard extended to SAFETY_UI (keys + equal bullet-list lengths per
language). New cross-engine test covers the page's sections, localization,
RTL, and the editor/board links. Full suite 354 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 14:53:48 -04:00
thePR0M3TH3ANandClaude Opus 4.8 53374ec87b Add NIP-56 reporting to the bulletin board
Others' flyers now offer a "Report" action (in the ⋯ menu, alongside
Block) that publishes a signed kind-1984 report: a category picker
(spam / illegal / impersonation / other → NIP-56 report types) plus an
optional free-text reason, tagged with the flyer (e) and author (p) so
other clients and moderators can aggregate it. Reporting also hides the
flyer locally, pairing with the existing per-device blocklist. Requires a
usable signer (device key or NIP-07); pasted-nsec viewers get a clear
"connect to report" message, same as delete.

Adds 11 localized strings across all 14 languages (i18n parity passes).
Test: report publishes a kind-1984 event with the right p/e tags + type +
reason and hides the row, across all 6 engines. Full suite 342 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 14:05:12 -04:00
thePR0M3TH3ANandClaude Opus 4.8 b344f92be7 Import/export a flyer design as a portable JSON file
Replaces the raw "Export event JSON" button with Import design / Export
design in the editor. A design file is content + relays only —
deliberately identity-free (no npub, naddr, event id, or signature, all
of which are derived at publish), so it can be handed to anyone and
published under their own key.

Format: { type: "voxvera_flyer_design", version: 1, flyer: <payload>,
relays: [] }. Export serializes the current form as-is (draft-friendly).
Import reads the picked file (capped at 256 KB), accepts the wrapper
format or a bare voxvera_flyer payload, then shows a confirm modal before
overwriting the editor; on confirm it fills the form and restores relays.
Imported content stays untrusted: the renderer escapes every field and
strict validation still runs at publish.

Adds 8 localized strings across all 14 languages (i18n parity passes).
Tests: export round-trip (identity-free assertions), import-then-confirm,
cancel-leaves-unchanged, and invalid-file rejection, across all 6
engines. Full suite 336 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 13:42:43 -04:00
thePR0M3TH3ANandClaude Opus 4.8 a4cb8dffcb Add NIP-46 remote signer (bunker) as an editor identity
The editor can now sign flyers via a NIP-46 remote signer: paste a
bunker:// link and the user's secret stays in their signer app
(nsec.app, Amber, …) while the browser holds only an ephemeral local
key. The vendored nostr-tools bundle has no nip46 module, so the client
is hand-rolled over a raw WebSocket — kind-24133 JSON-RPC encrypted with
NIP-44 (with a NIP-04 decrypt fallback), running connect →
get_public_key → sign_event. Connections are session-only; the bunker
token is never written to disk, so a reload returns to anonymous.

Switching to/from any other identity mode disconnects the signer, and a
successful connection mirrors the user pubkey to the board's connected
key. Adds 9 localized strings across all 14 languages (i18n parity test
passes).

Tested with a fake relay/signer doing real NIP-44 crypto: full connect →
get_public_key → sign_event → publish round-trip, across all 6 engines.
Full suite 312 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 13:17:17 -04:00
thePR0M3TH3ANandClaude Opus 4.8 bf90f1608b Degree-2 web-of-trust + editor↔board identity sync
Board trust filter now also includes follows-of-follows (degree 2): on
connect it fetches the viewer's NIP-02 follows, then the kind-3 lists of
those follows (capped at 150 authors), and trusts the union. Brand-new
keys still fall back to the curator seed; no-data still falls back to
show-all.

A deliberate editor identity choice (NIP-07 / imported / generated /
unlocked nsec / anonymous) now writes a shared local pubkey that the
board reads, so the same person sees a consistent identity across pages.

Tests: degree-2 inclusion (A direct, B friend-of-friend shown; stranger
C hidden) and editor→board key sync, across all 6 engines. Full suite
306 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 10:36:30 -04:00
thePR0M3TH3ANandClaude Opus 4.8 c97624a5e0 Board "My flyers" filter + author display names; editor wrong-PIN lockout
Three quick-ish wins:

- "My flyers" board filter: a checkbox that shows only the connected user's own
  flyers (overrides the trust filter, since your own may not be in your follow
  graph). New mine_only string × 14.
- Author display names: the board fetches authors' kind-0 metadata and shows
  the display name (falling back to the npub) instead of a bare key. Names are
  untrusted — escaped at render and length-capped. (NIP-05 verification is still
  future.)
- Wrong-PIN lockout: after 3 bad PINs the editor refuses to unlock a remembered
  nsec for a 30s cooldown, persisted across reloads so it isn't reset by a
  refresh. A deterrent against casual on-device guessing (a leaked blob is still
  offline-brute-forceable). New too_many_attempts string × 14.

Adds e2e for each; the i18n guard enforces the new strings in all 14 languages.
294 e2e across all six engines; roadmap updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 10:20:16 -04:00
thePR0M3TH3ANandClaude Opus 4.8 78ee5bf0cf Add i18n key-parity guard + publish "N/M relays accepted"; fix missing translation
Two quick, high-value wins:

- i18n guard (e2e/i18n.spec.js): a pure-Node test that fails if any NOSTR_UI /
  BOARD_UI / locales dictionary is missing or has an extra key in some language.
  It immediately caught field_too_long, which was English-only — so non-English
  users saw the English "that field is too long" message. Added the missing
  translation to all 13 other languages.

- Publish transparency: the confirmation modal now shows "Relays accepted: N / M"
  (relays_accepted string × 14) and flags a zero-acceptance publish in red, so a
  weak publish is visible immediately instead of discovered later.

276 e2e across all six engines; roadmap updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 10:04:18 -04:00
thePR0M3TH3ANandClaude Opus 4.8 d160542f7d Default flyer: swap title/headline so the side tabs carry the project name
The tear-off tabs are labeled with the flyer's title, so swap the default
landing content's title and headline in every language: the title is now
"OPERATION VOX VERA" (the project name, shown on all ten tabs and as the top
line) and the headline is the dramatic "TOP SECRET" below it. Applied across all
14 languages in locales.js (e.g. de title "OPERATION VOX VERA" / headline
"STRENG GEHEIM").

Updated the e2e assertions (default title is now the project name; the German-
distinctive string is now the headline) and the spec payload example. 258 e2e
across all six engines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 09:30:28 -04:00
thePR0M3TH3ANandClaude Opus 4.8 90e1bc8873 Mobile: flyer fits width, board collapses to cards, fix always-shown identity box
Three mobile/responsive fixes (diagnosed at phone widths):

- Flyer no longer side-scrolls/clips on phones. In viewer mode .flyer-preview is
  a flexbox, so the sheet (a flex item) was shrunk to the viewport, making the
  fit math compute scale = 1 and the full-width content clip. Add flex: 0 0 auto
  so the sheet keeps its true print width and the transform scales it to fully
  fit the screen.

- Bulletin board collapses to a card per flyer on ≤640px: title + the ⋯ menu
  stay visible; the secondary columns (link, posted-by, language, date, event
  id) move behind a per-row "Details" disclosure (labeled via data-label). New
  details_label string × 14 languages.

- Fix a real (not mobile-only) bug: the editor's nsec-import box and
  "locked identity" row showed on every load because .identity-import's
  display:flex overrode the hidden attribute. Add .identity-import[hidden]
  { display:none } (same class of bug we hit on the board identity chip).

Adds e2e: flyer fits width with no horizontal overflow, identity controls hidden
until chosen, and the board's narrow-screen card + Details toggle. The desktop
"sortable columns" test pins a desktop viewport (the header is hidden in card
mode). 258 e2e across all six engines; docs updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 09:20:33 -04:00
thePR0M3TH3ANandClaude Opus 4.8 053ea875b8 Tear-off tabs: title label, clickable "open & reprint" link, VoxVera.org event ID
Each of the ten tear-off tabs now reads, top to bottom:

- the flyer's TITLE, auto-sized (fitTabTitles) to be as large as fits the ~1in
  tab — big for short titles, shrinking instead of overflowing for long ones —
  so every tab is clearly labeled;
- a smaller, clickable "Open & reprint this flyer" link (the <a href>, so it
  stays live in a printed PDF) in place of the old "Click to open this poster."
  / "Share and reprint." (clicking, not "scan," is what a PDF link does);
- the tear-off QR; and
- a "Reopen at VoxVera.org — Event ID:" line (was just "Event ID:") so people
  know the id is for re-finding/reprinting the flyer at the site.

New tab_link / tab_event_id strings in all 14 languages (VoxVera.org stays the
Latin brand; the rest localizes). e2e covers the title, the link text + poster
href, and the event-id label. 240 e2e across all six engines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-11 08:29:13 -04:00
thePR0M3TH3ANandClaude Opus 4.8 085ff81c1a Board: tuck per-row actions behind a ⋯ menu
The inline Edit / Re-broadcast / Delete (and Block) buttons wrapped messily in
the actions column. Replace them with a single ⋯ button per row that opens a
small popout menu with the relevant actions. Closes on outside click, Escape,
or selecting an item; the actions and their data-act wiring are unchanged.

Adds the menu markup/toggle in board.js, an "Actions" aria-label string × 14
languages, and menu/kebab styles. e2e updated to open the menu before acting.
234 e2e across all six engines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 20:16:12 -04:00
thePR0M3TH3ANandClaude Opus 4.8 73c28f31ec Bulletin board: per-row manage (edit / re-broadcast / delete) + local Block
Add inline management to the board for the connected viewer:

- On your own flyers: Edit (links to /?edit=1#<naddr>, which the main client
  now honors by loading the flyer straight into the editor), Re-broadcast
  (re-sends the already-signed event to all relays — no signing), and Delete
  (replaceable tombstone + NIP-09, signed via the device key or NIP-07; a
  pasted-nsec connection is pointed to the editor where the key is available).
- On flyers from others: Block — a per-device blocklist of author pubkeys
  (voxvera_blocked_pubkeys) that always hides them, with a "Clear blocked"
  control. This is roadmap #1's local blocklist direction.

The board hides tombstoned flyers (newest-version-wins so a delete can't be
undone by an older copy). New non-sortable actions column; a board delete-
confirm modal; 12 new strings × 14 languages. Adds e2e for block (hide/persist/
clear), own-vs-other actions, and board delete (tombstone + NIP-09 published),
plus a ?edit=1 deep link. 234 e2e across all six engines; docs updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 20:06:07 -04:00
thePR0M3TH3ANandClaude Opus 4.8 9b03ada43a Flyers: edit, re-publish (keep-alive), and delete (tombstone + NIP-09)
Authors can now manage a published flyer from the editor when it's loaded from
an event (author-only — the active signing key must match the flyer's author):

- Edit: change fields and publish again; the same d-tag replaces in place.
- Re-publish (keep-alive): re-sign + re-broadcast the current content so relays
  that prune app-data don't age the flyer out. Partially addresses roadmap #4.
- Delete: belt-and-suspenders across relay behaviors —
  1) a replaceable TOMBSTONE (same kind+d, payload deleted:true + a ["deleted"]
     tag) that overwrites the flyer's content, so the board/viewer hide it even
     on relays that ignore deletions, and
  2) a NIP-09 deletion request (kind 5) for the addressable a-tag + event id.
  Confirmed via a modal; framed as best-effort, not a guarantee.

Tombstone recognition: the board's parseFlyers keeps the newest event per
(pubkey, d) and skips it when that newest version is a tombstone (so a delete
can't be reversed by an older copy arriving later); the viewer renders a
localized "This flyer was removed." state.

Adds the loaded-flyer actions UI + a delete-confirm modal, 11 new strings ×
14 languages, a Python schema test for the tombstone payload, and e2e for the
delete flow, board hiding, and the viewer removed state. 216 e2e across all six
engines; docs (spec/roadmap/README) updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:39:23 -04:00
thePR0M3TH3ANandClaude Opus 4.8 23badb03ae Add an SVG favicon (no more /favicon.ico 404)
Link a small inline SVG (site/favicon.svg, a red tile with a white "V") from
both index.html and board.html via a root-absolute href, so it resolves from
"/" and from "/board/" under Vercel cleanUrls. With an icon link present,
browsers stop auto-requesting (and 404ing on) /favicon.ico. No binary asset and
no external dependency, consistent with the self-contained client.

e2e asserts both pages declare the favicon and that /favicon.svg is served as
valid SVG. Closes the last code-finishable item in the roadmap's smaller list.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 19:19:06 -04:00
thePR0M3TH3ANandClaude Opus 4.8 2f423ceed5 Editor: build the poster naddr from the actual signing key (fix wrong-author URLs)
A flyer published via NIP-07 could get a poster naddr encoding the anonymous
device key instead of the signer: signActiveEvent signed through window.nostr
(the real key) while activeIdentity() — used to build the naddr — fell back to
the anon key whenever nip07Pubkey hadn't loaded yet (e.g. just after a reload
restored nip07 mode). The resulting poster URL pointed at the wrong author and
could never be loaded back, even though the flyer appeared on the board (which
finds events by tag, not address).

signAndPublish now resolves the concrete signing pubkey first (fetching the
NIP-07 key if needed), builds the naddr from that exact key, and refuses to
publish if the signed event's pubkey does not match the naddr — so the signer
and the poster address can no longer diverge.

Note: this prevents new broken flyers; an already-published flyer with a
mismatched naddr must be re-published to get a correct poster URL.

Test: a NIP-07 publish issued while getPublicKey is still pending now yields a
naddr whose pubkey matches the signer (fails on the old code). 192 e2e across
all six engines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:53:03 -04:00
thePR0M3TH3ANandClaude Opus 4.8 0530dbc905 Bulletin board: add an Event ID column with a per-row copy button
Each flyer row now shows its event ID (shortened, full value in the title
attribute) with a Copy button that copies the full 64-char id to the clipboard
(flips to "Copied", falls back to selecting the text if the clipboard is
blocked). The column is sortable like the others. Copy is handled by a single
delegated listener on the tbody, since rows re-render on sort.

Localized the new column header (col_event_id) across all 14 languages; Copy/
Copied already existed in the board strings. Updated the column e2e test (now 6
columns) and added a test for the id cell + copy target. Docs updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:42:20 -04:00
thePR0M3TH3ANandClaude Opus 4.8 82126d52d6 Viewer: render any flyer the board lists (lenient view + board-style fetch)
A flyer could appear on the bulletin board yet fail to open in the viewer.
Two causes, both fixed so "if the board shows it, the viewer opens it":

- Over-strict view validation. The viewer ran the *authoring* validatePayload
  on fetched events (in payloadFromEvent and normalizePayload) and threw on
  length caps, a raw-HTML regex that false-positives on plain text like
  "value < x >", version, and URL scheme — refusing to render. The renderer
  already HTML-escapes every field, so the view path is now lenient: it renders
  what was published and only neutralizes unsafe URL schemes (javascript:/data:
  become no-link instead of throwing). Strict validation still gates authoring
  via buildPayloadFromForm, so we never publish junk. The mandatory t=flyer tag
  is dropped (t=voxvera + a voxvera_flyer payload is enough), matching the board.

- Fragile fetch. fetchReplaceable relied on the relay honoring an addressable
  "#d" tag filter; some relays don't, yet still return the event for a broader
  query (which is how the board finds it). It now queries by author+kind and
  matches the d-tag in-page, with a longer timeout.

Tests: a fetched flyer with HTML-ish text + a 6k body + a javascript: URL now
renders with the URL neutralized; and a flyer served by a relay that refuses
"#d" filters still resolves. 180 e2e across all six engines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 15:29:17 -04:00
thePR0M3TH3ANandClaude Opus 4.8 be91ea7250 Roadmap: mark the trust model (#1–#2) shipped
Add a "Shipped so far" summary (board login gate, web-of-trust filter, editor
publishing identity with PIN-encrypted nsec) and refresh the review footer:
the trust model is now implemented, so #3 (centralized client distribution) is
the highest-leverage untouched item.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 10:22:27 -04:00
thePR0M3TH3ANandClaude Opus 4.8 861407cbf6 Editor: publish under your own identity (NIP-07 / nsec) with PIN-encrypted nsec
Let authors publish a flyer under a stable, identifiable key when they want
one, while keeping anonymous-by-default. The editor now signs under one of
three identities, routed through activeIdentity()/signActiveEvent():

- Anonymous (default, unchanged): the per-device key, signed locally.
- NIP-07 extension: signs via window.nostr.signEvent; the secret never
  enters the page.
- Imported nsec: decoded to a key used to sign. By default the secret lives
  only in memory for the session. "Remember on this device" stores it
  encrypted with a PIN (PBKDF2 SHA-256, 600k iterations, random salt ->
  AES-GCM-256); the plaintext secret is never written to storage. On reload a
  remembered key is locked until the PIN is entered; a session-only key falls
  back to anonymous.

The signer pill, author npub, naddr, and poster URL all follow the active
identity. New identity controls are localized across all 14 languages.

Security note (also in docs): a short numeric PIN is brute-forceable offline
if the encrypted blob leaks, so it guards casual snooping, not a targeted
attacker — the NIP-07 path is stronger, and NIP-46 is a documented follow-up.
The minimum PIN is 4 digits but longer is allowed and encouraged.

Also updates README, the client spec (new "Publishing identity" section),
the roadmap (#1/#2 marked in progress with phases shipped), and the e2e
README. Covered by the cross-engine Playwright suite (default-anonymous,
NIP-07 connect + publish, session-only nsec without persistence, invalid
nsec, PIN encrypt -> lock on reload -> wrong/right PIN -> unlock, and forget).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 10:16:53 -04:00
thePR0M3TH3ANandClaude Opus 4.8 1297a73332 Bulletin board: login gate, web-of-trust filter, and key connect methods
Gate the board behind a connected Nostr identity and filter what it shows
to the viewer's web of trust, so it is no longer an unmoderated firehose.

- Login gate: the board shows a Connect prompt instead of the table until a
  Nostr identity is connected; the connected npub is surfaced with a
  Disconnect control and remembered (pubkey only) for silent reconnect.
- Connect methods: NIP-07 extension, pasting an nsec (decoded in-page to a
  pubkey, then discarded — the board reads, never signs), or creating a new
  key (reveals the nsec to save, reuses the device key if present).
- Web-of-trust filter (NIP-02): defaults to flyers from authors in the
  viewer's follow graph, with a "Show all" opt-out. A fresh key with no
  follows is seeded from a pinned curator account so new viewers still get a
  curated board; if no trust data loads at all, it shows everything.
- Localized across all 14 languages (gate, filter, and connect strings),
  RTL included.

Covered by the cross-engine Playwright suite (gate shown/hidden, connect via
each method, pubkey-only persistence, web-of-trust filtering + show-all, and
the curator-seeded fallback).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 10:16:24 -04:00
thePR0M3TH3ANandClaude Opus 4.8 b92cb018c5 Roadmap: plan identity + web-of-trust gating for the bulletin board
Mark board moderation (#1) and authenticity (#2) as Planned and add a joint
"identity + web-of-trust on the board" direction:

- Login enables a trust-filtered view, not access control (you cannot gate
  writes to public relays; you filter what the board displays).
- Keep anonymous creation + printing available; require identity only to
  appear on the public board.
- Web-of-trust (NIP-02 follow graph) is the real spam defense, since keys are
  free; login alone is just a soft deterrent.
- Support NIP-07 plus nsec import / NIP-46 / promoted anon key so mobile (the
  scanned-QR audience) isn't locked out.
- Phased plan (identity → WoT filter → blocklist/report/NIP-05) and the two
  open decisions: gate viewing or not, and which login methods at launch.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 22:25:38 -04:00
thePR0M3TH3ANandClaude Opus 4.8 c8c715f438 Document known issues and roadmap
Add docs/roadmap.md capturing product/threat-model gaps that are not code
bugs, in priority order: (1) bulletin board has no moderation/abuse story,
(2) anonymous keys give no authenticity, (3) client distribution is
centralized on one domain, (4) relay permanence is overstated, (5) minimal
content-safety guardrails, plus smaller items. Each entry notes the problem,
current mitigation, why it matters, and possible directions.

Link it from the README and the spec's bulletin board section.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 22:21:34 -04:00
thePR0M3TH3ANandClaude Opus 4.8 e0ea31eaef Widen paper-size selector and balance bulletin-board link spacing
- Give the paper-size selects a min/max width so "US Letter (8.5 × 11 in)"
  is readable instead of truncated.
- Add equal spacing below the bulletin-board link to match the gap above it.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 22:15:03 -04:00
thePR0M3TH3ANandClaude Opus 4.8 7ab7e412ec Show a publish confirmation modal with the flyer URL
After Sign and publish succeeds, open a modal that tells the author the
flyer is published and shows its URL (pre-selected) with Copy and Close
buttons, so they know the process completed and where to view/share it.

- New modal markup in index.html + overlay/box styles (hidden in print).
- Closes on the Close button, backdrop click, or Escape; Copy writes the URL
  to the clipboard with a "Copied" confirmation.
- Localized for all 14 languages (published_title / published_message /
  close); the URL is result.posterUrl.
- Playwright: add a test that stubs the relay socket, publishes, and asserts
  the modal shows the localized text and the naddr poster URL. 84 tests pass
  across all engines.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 22:06:30 -04:00
thePR0M3TH3ANandClaude Opus 4.8 0044b4b797 Fix unstyled bulletin board on Vercel (cleanUrls/trailingSlash)
vercel.json sets cleanUrls + trailingSlash, so board.html is served at
/board/. Its relative asset paths (nostr-client.css, board.js, locales.js,
vendor/...) then resolved to /board/... and 404'd, leaving the page as raw
unstyled HTML stuck on "Loading…". Use root-absolute paths instead, and
point the back link at "/". Add an e2e guard asserting board.html assets are
root-absolute (the dev server can't reproduce Vercel's URL rewrites).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 21:38:26 -04:00
thePR0M3TH3ANandClaude Opus 4.8 98086a0ad4 Add bulletin board page listing recent flyers from the relays
A small screen-only link below the flyer preview (outside the printable
sheet, hidden in print) opens board.html: a spreadsheet-style list of the
most recent VoxVera flyers found on the default relays.

- Columns: title, link (the poster URL shown as "link"), posting npub,
  language, and posted date; every column is sortable (default date
  descending). Replaceable events are de-duplicated per author+d identifier,
  keeping the latest.
- board.js is independent of nostr-client.js (which exports nothing): its UI
  strings live in BOARD_UI for all 14 languages, and it reuses the vendored
  nostr-tools and locales.js (language names + RTL direction).
- The board has its own language selector that reads/writes the same
  voxvera_nostr_lang localStorage key as the main client, so the choice
  carries across pages; switching it re-localizes the chrome, headers,
  language labels, and date formatting and flips RTL.
- Untrusted relay content is escaped, and link URLs are rejected unless
  http(s) so javascript:/data: never render as a link.

Docs updated (README, spec, AGENTS). Playwright: add tests for the
out-of-sheet print-hidden link, the localized sortable columns, and the
board language selector (incl. RTL + persistence). 72 tests pass across
Chromium, Firefox, WebKit, and Android/iOS mobile emulations.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 16:28:32 -04:00
thePR0M3TH3ANandClaude Opus 4.8 127f9c3134 Locale-aware defaults: browser language list + US Letter/A4 paper size
Language inference:
- Walk navigator.languages (the full ordered preference list), not just
  navigator.language, and pick the first base language we support. A user who
  prefers e.g. Dutch then German now gets German instead of the English
  fallback. De-duplicates the default-language resolution into setDefaultText.

Paper size (US Letter + A4):
- Add A4 (210x297mm) alongside US Letter (8.5x11in). Sheet dimensions move to
  CSS custom properties (--sheet-w/--sheet-h) toggled by a paper-letter/paper-a4
  class on <html>; the tear-off column and QR codes stay fixed physical sizes
  and the content column flexes.
- @page size is injected by JS (id voxvera-page-size) since @page cannot read
  CSS variables across engines; the print block keeps margin:0 and a Letter
  default for the pre-JS state.
- Default paper is chosen from the browser locale's region subtag
  (en-US -> Letter, en-GB/de-DE -> A4) — "by location" without geolocation,
  falling back to Letter when no region is present. A manual override selector
  (topbar + viewer controls) persists in localStorage and wins over the default.
- Paper size is a local print/view preference and is never written into the
  Nostr event, so a flyer reprints on either size unchanged.
- Add paper_size/paper_letter/paper_a4 labels for all 14 languages.

Docs + tests:
- Spec gains a "Print paper size" section; AGENTS.md records the
  not-in-payload invariant; README adds a feature bullet; the field-limits
  cheat sheet gains Letter/A4 columns (re-measured per paper size).
- Playwright: add tests for navigator.languages inference, region-based paper
  default (A4 and Letter), and override persistence. 54 tests pass across
  Chromium, Firefox, WebKit, and Android/iOS mobile emulations.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 15:56:25 -04:00
thePR0M3TH3ANandClaude Opus 4.8 c5b2c20fc9 Add cross-browser e2e tests, field-limit cheat sheet, and docs
Verification:
- Add Playwright harness (e2e/) covering Chromium, Firefox, and WebKit plus
  Android/iOS mobile emulations. Smoke tests assert: default flyer renders
  with no JS/console errors, @media print hides app chrome and keeps the
  flyer, anonymous npub generation (WebCrypto), legacy /nostr/ redirect
  preserves query+hash, and event-lookup URLs show the loading state instead
  of the default flyer. All six projects pass.
- package.json/playwright.config.js wire up the projects and a managed static
  server; .gitignore excludes node_modules and Playwright output.

Docs:
- docs/flyer-field-limits.md: per-script character cheat sheet for each flyer
  field (single-line caps + multi-line guidance), measured by rendering each
  script at print size.
- README: add a Features section matching the shipping client and a
  cross-browser verification section; link the cheat sheet.
- AGENTS.md: document the e2e suite and the one-time WebKit Linux dep.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 15:29:49 -04:00
thePR0M3TH3ANandClaude Opus 4.8 562b7d1429 Show loading state on event-lookup URLs instead of default flyer
A viewer arriving via an event-lookup URL (naddr/nevent/note/hex in the
query or #fragment) previously saw the default placeholder flyer flash
before the real event resolved. Now it renders a localized "Loading
content..." state, and a "Could not load this flyer." state on fetch
failure. Default flyer content now appears only on the bare main URL.

- Add loading_content and load_failed labels for all 14 languages
- Add renderStatusPreview() rendering a minimal flyer sheet with a single
  centered, direction-aware message (no tear-offs, no QR)
- Render loading state synchronously before fetch in the URL-event path,
  failed state in the catch handler
- Add .flyer-status-message CSS

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 13:46:43 -04:00
thePR0M3TH3ANandClaude Opus 4.8 77bfef1bbe Serve client at site root; redirect legacy /nostr/
The /nostr/ path was a leftover from when the site also hosted a Tor
client. The app is Nostr-only now, so move the client to the site root
and shorten poster URLs to https://voxvera.org/#naddr1... (drops both
/nostr/ and the ?addr= query key, for a cleaner/smaller tear-off QR).

- Move site/nostr/{nostr-client.js,css,locales.js,vendor} to site/.
- site/index.html is now the client; site/nostr/index.html is a redirect
  stub that forwards /nostr/ to / while preserving the query string and
  #naddr fragment, so older poster URLs and printed QR codes still
  resolve.
- Carry the naddr in the URL fragment (#) instead of ?addr=; the viewer
  reads query, fragment, or the full URL, so old links keep working.
- Update README, AGENTS.md, and the spec to the new paths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 10:49:36 -04:00
thePR0M3TH3ANandClaude Opus 4.8 828ac3a624 Tolerate whitespace in pasted event ID lookups
The Load Event field requires 64 contiguous hex chars, but the event ID
printed on a tear-off tab wraps across two lines, so copying it back in
pastes as two 32-char halves separated by a newline/space and failed to
match. Strip inner whitespace before matching the raw hex id, which also
tolerates a pasted "Event ID:" label prefix. naddr/note/nevent inputs are
unaffected.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 10:37:07 -04:00
thePR0M3TH3ANandClaude Opus 4.8 aedb381dfb Improve flyer tear-offs, shorten poster URL, adaptive tracking
- Tear-off tabs: single hyperlinked statement (no printed URL), with
  "Share and reprint." on its own line; localize "Click to open this
  poster." across all 14 languages.
- Show the full event ID, bottom-anchored on each tab, split into two
  32-char lines at max readable monospace size.
- Shorten the poster URL by encoding the naddr with a single relay hint
  instead of the full relay list (~233 -> ~171 chars) for an easier-to-
  scan QR; still a standard naddr, older URLs resolve unchanged.
- Add per-script letter-spacing policy (ls-full/ls-cjk/ls-none) to fit
  more text per line where safe, and turn tracking off for Arabic,
  Persian, and Hindi (fixes broken cursive joins / combining marks).
- Move the "field too long" warning inline under the offending field
  instead of a single bottom status line.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 10:13:55 -04:00
thePR0M3TH3AN 9baf5fb195 fixed mobile 2026-06-06 14:38:19 -04:00
thePR0M3TH3AN fd272888ec Move VoxVera credit below footer message 2026-06-06 14:31:14 -04:00
thePR0M3TH3AN d273843bb1 Improve flyer editing and add demos 2026-06-06 14:26:40 -04:00
thePR0M3TH3AN 20d5e64326 Localize Nostr editor status UI 2026-06-06 09:28:10 -04:00
thePR0M3TH3AN c08f8509db Truncate visible tear-off URLs 2026-06-06 09:19:23 -04:00
thePR0M3TH3AN c53f87dd95 Update default flyer copy for Nostr 2026-06-05 16:01:32 -04:00
thePR0M3TH3AN 83b4133780 Add Vercel static deployment config 2026-06-05 13:59:27 -04:00
thePR0M3TH3AN 1bda451604 Flag flyer language in Nostr events 2026-06-05 13:33:18 -04:00
thePR0M3TH3AN 5afda54a21 Keep content URL separate from poster URL 2026-06-05 10:18:03 -04:00
thePR0M3TH3AN 9aa90773e6 Remove legacy Tor app from main 2026-06-05 10:13:41 -04:00
thePR0M3TH3AN ee13d3fd03 Localize Nostr flyer side tabs 2026-06-05 10:01:57 -04:00
thePR0M3TH3AN 8619261c78 Localize Nostr language controls 2026-06-05 08:51:18 -04:00
thePR0M3TH3AN 3738204ede Keep viewer controls in layout 2026-06-05 08:29:40 -04:00
thePR0M3TH3AN fc50962bf4 Center Nostr viewer controls 2026-06-05 08:22:46 -04:00
thePR0M3TH3AN 0f94d640ac added language selector 2026-06-04 22:30:50 -04:00
thePR0M3TH3AN 46fa306c31 Localize VoxVera Nostr client 2026-06-04 22:06:22 -04:00
thePR0M3TH3AN d415eb26a3 Add VoxVera Nostr static client 2026-06-04 20:15:50 -04:00
thePR0M3TH3AN 285d730369 chore: release v0.3.12 2026-03-19 22:52:47 -04:00
thePR0M3TH3AN f5403448a7 fix: stabilize docker runtime smoke checks 2026-03-19 22:49:05 -04:00
thePR0M3TH3AN 6822b8e496 fix: add docker runtime smoke job to ci 2026-03-19 22:36:36 -04:00
thePR0M3TH3AN 83e9e3d511 feat: harden docker runtime validation 2026-03-19 22:30:28 -04:00
thePR0M3TH3AN 004cce2a4f chore: release v0.3.11 2026-03-19 22:05:16 -04:00
thePR0M3TH3AN ec0abd856c chore: release v0.3.10 2026-03-19 21:45:02 -04:00
thePR0M3TH3AN 7724ac78a7 chore: release v0.3.9 2026-03-19 21:36:04 -04:00
thePR0M3TH3AN c1a5e608de chore: release v0.3.8 2026-03-19 16:53:04 -04:00
thePR0M3TH3AN 44cf452f67 chore: release v0.3.7 2026-03-19 16:36:46 -04:00
thePR0M3TH3AN 4e49762a85 docs: add linux hosting validation runbook 2026-03-19 16:24:22 -04:00
thePR0M3TH3AN 2de7ea073c feat: add autostart lifecycle controls 2026-03-19 16:15:51 -04:00
thePR0M3TH3AN 2dd0cf4ed8 fix: install smoke test dependencies in ci 2026-03-19 16:03:55 -04:00
thePR0M3TH3AN 6c4d6d6aef feat: formalize platform runtime contracts 2026-03-19 15:57:50 -04:00
thePR0M3TH3AN e6ff21c069 chore: release v0.3.6 2026-03-17 16:16:17 -04:00
thePR0M3TH3AN 1fcb86ac8b chore: release v0.3.5 2026-03-17 15:35:17 -04:00
thePR0M3TH3AN 2b29d71ac5 chore: release v0.3.4 2026-03-17 14:18:42 -04:00
thePR0M3TH3ANandClaude Opus 4.6 46360ca6ea fix(install): clean stale generated files from site dirs before rebuild
Install script now removes index.html, QR codes, and download bundles
from existing site directories before rebuild-all regenerates them.
Preserves config.json, .onionshare-session, server.pid, and logs.
Also auto-rebuilds on serve if index.html is missing.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:46:14 -04:00
thePR0M3TH3ANandClaude Opus 4.6 03b1c51e27 feat(rebuild): add rebuild-all command and auto-rebuild on install
Existing sites kept stale index.html from old templates after upgrades.
New `voxvera rebuild-all` regenerates HTML for all sites from the
current master template while preserving keys, config, and content.
The install script now calls rebuild-all automatically after install.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:39:34 -04:00
thePR0M3TH3ANandClaude Opus 4.6 fc628fb9a0 fix(templates): sync bundled index.html copies with master template
These files are not read by the build pipeline (which always generates
from index-master.html), but keeping them in sync avoids confusion.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:33:47 -04:00
thePR0M3TH3ANandClaude Opus 4.6 91391b1dd7 fix(lint): add missing Console() instances in export/import key functions
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:12:35 -04:00
thePR0M3TH3ANandClaude Opus 4.6 d61a7b4c92 feat(keys): add Export Keys / Import Keys to per-site menu with 14-language support
Allows users to export onion service session keys as base64 and import
them on another instance, enabling domain migration without re-hosting.
Bump version to 0.3.3.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:04:34 -04:00
thePR0M3TH3ANandClaude Opus 4.6 2dabd93809 bump(v0.3.2): version bump for new release
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 19:51:37 -04:00
thePR0M3TH3ANandClaude Opus 4.6 ee5024678d fix(ui): tighten header spacing, preserve body edits on retry, add over-limit warning
- Switch content panel from space-between to flex-start so header
  elements (title, subtitle, headline) pack tightly instead of
  spreading across the full height
- Message area uses flex:1 to fill remaining space and keep footer
  at the bottom
- Body editor now preserves user text when re-editing after exceeding
  the 1000 character limit (previously reset to default)
- Show a yellow warning when user proceeds with over-limit body text
- Add body_over_limit_warning locale key across all 14 languages

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 19:41:40 -04:00
thePR0M3TH3ANandClaude Opus 4.6 445375e098 feat(templates): add "Create From Template" menu with 14-language support
- Add bundled preset templates under voxvera/templates/presets/ (moved
  from imports/, normalized field names to match config schema)
- New "Create From Template" option in the manage menu lets users pick
  a preset (aosp, bitcoin, jesus, meshtastic, nostr, voxvera) and
  customize it before publishing
- Template content is fully translated across all 14 supported languages;
  the displayed content matches the user's selected language
- Rebuild HTML files with updated locale data

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 18:55:38 -04:00
thePR0M3TH3ANandClaude Opus 4.6 9067596083 bump(v0.3.1): version bump, prefer tarball install, translate locale names
- Bump version to 0.3.1 for new release with all content fixes
- Reorder install strategies: tarball first (always current with main),
  pre-built binary last (can lag behind)
- Translate the "name" field across all 14 locale files
- Rebuild HTML files with updated locales

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 14:57:08 -04:00
thePR0M3TH3ANandClaude Opus 4.6 d732b355d7 fix(install): clean up stale state, fix binary detection, and add hash reset
- Stop deleting pip/pipx Python entry points by narrowing the stale
  binary check to ELF/Mach-O only (not generic "executable")
- Clean up stale pip wheel cache, /tmp leftovers, ~/config.json and
  ~/imports/ from older VoxVera versions (preserves ~/host tor sites)
- Run hash -r after install and prompt user to do the same if command
  is not found (curl|bash runs in a subshell)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 14:02:49 -04:00
thePR0M3TH3ANandClaude Opus 4.6 bcc8616851 fix(content): apply newline-to-br conversion in build_site locale embedding and rebuild HTML
The build_site() path was missing the _newlines_to_br() call when
embedding user content into the locales JSON, causing literal \n to
appear in the generated index.html files. Fixed and rebuilt both
site/index.html and voxvera/host/voxvera/index.html.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:54:04 -04:00
thePR0M3TH3ANandClaude Opus 4.6 c9f2546e1d fix(content): complete newline/br round-trip for editor and all content sources
Convert <br> to real newlines before opening the editor so users see
proper paragraph breaks instead of raw markup. Ensure user-typed newlines
are converted back to <br> when embedded in the locales JSON for JS
rendering. Replace remaining \n with <br> in all import configs and
site/config.json for consistency.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:46:28 -04:00
thePR0M3TH3ANandClaude Opus 4.6 6189ae321e fix(content): robust newline-to-br conversion across all content sources
Root cause: three config.json files (src, templates, host) had different
newline encodings (\n, \\n, raw newlines). The templates version had
double-escaped \\n\\n which survived the simple replace chain.

- Replace \n with <br> in all three config.json files
- Replace raw newlines with <br> in bundled host/voxvera/index.html
- Add _newlines_to_br() helper that handles all encoding variants:
  real newlines, literal \n, already-converted <br>, and mixed content
- Use the helper in all four HTML injection sites in cli.py

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:17:12 -04:00
thePR0M3TH3ANandClaude Opus 4.6 945557cd0f fix(locales): replace \n with <br> in default content across all languages
Default landing content in all 14 locale files used JSON \n for
paragraph breaks. These rendered as literal \n\n on Tor Browser
(no JS). Now uses <br><br> tags directly in the locale strings.
User-entered content from the editor still gets \n→<br> conversion
at build time via cli.py.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:04:53 -04:00
thePR0M3TH3ANandClaude Opus 4.6 389d1f05a5 fix(install): add wget fallback, timeouts on all operations, reorder strategies
The Debian Bookworm SSL/TLS stack (both OpenSSL for curl and GnuTLS
for git) frequently fails or hangs against GitHub. This overhaul:

- Adds wget as fallback downloader (different TLS codepath)
- Wraps all git/network ops with `timeout` to prevent hangs
- Reorders strategies: binary → tarball → shallow clone → pipx → pip
  (tarball now before git-based methods since it needs no git protocol)
- Each download retries 3x with both curl and wget before giving up
- Installs wget as a system dep on apt-based distros
- Final error message gives manual download instructions

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:58:53 -04:00
thePR0M3TH3ANandClaude Opus 4.6 ba3c63749d fix(install): remove stale binary when pipx/pip fallback succeeds
A failed binary download (or leftover from a previous install) at
~/.local/bin/voxvera shadows the pipx/pip entrypoint script. Now all
non-binary strategies detect and remove stale ELF/Mach-O binaries
before declaring success.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:49:45 -04:00
thePR0M3TH3ANandClaude Opus 4.6 6839f67311 fix(install): resilient install with timeouts, retries, and tarball fallback
- Add GIT_HTTP_LOW_SPEED_LIMIT/TIME to prevent git clones from hanging
- Force TLS 1.2 to work around GnuTLS negotiation bugs on older Debian
- Add curl retries, timeouts, and --retry-connrefused
- Add GitHub tarball fallback (no git clone needed at all)
- Add shallow clone (--depth 1) as final fallback
- Refactor install strategies into a clean cascade: binary → pipx →
  pip git → tarball → shallow clone

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:46:37 -04:00
thePR0M3TH3ANandClaude Opus 4.6 b4a74a32f4 fix(cli): convert newlines to <br> in static HTML for Tor compatibility
User-entered line breaks were rendering as literal \n\n in the content
box because the static HTML injection didn't convert newlines to <br>
tags. Since Tor Browser disables JS, the fix must be server-side.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:39:26 -04:00
thePR0M3TH3ANandClaude Opus 4.6 bc64d1c3b5 feat(cli): persistent servers, start-all/stop-all, autostart on boot (v0.3.0)
Sites now survive terminal close and can start automatically on boot:

- subprocess.Popen uses start_new_session=True (Unix) or
  CREATE_NEW_PROCESS_GROUP (Windows) so onionshare-cli processes
  are fully detached and immune to SIGHUP when the terminal exits
- New `voxvera start-all` / `stop-all` commands for non-interactive
  batch control of all configured sites
- New `voxvera autostart` installs platform-appropriate boot service:
  - Linux: systemd user service + loginctl enable-linger
  - macOS: launchd LaunchAgent
  - Windows: schtasks scheduled task on login

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:00:44 -04:00
thePR0M3TH3ANandClaude Opus 4.6 b5ab447207 fix(cli): set PYTHONNOUSERSITE=1 for onionshare-cli subprocess, robust cleanup (v0.2.19)
Root cause: user-local werkzeug 2.0.3 in ~/.local/lib/python3.11/
site-packages shadows the system werkzeug 2.2.2, breaking Flask's
LocalProxy.unbound_message for the system onionshare-cli.

The pip3 show-based cleanup was unreliable (shows system package,
skips user-local). Two-pronged fix:

1. Set PYTHONNOUSERSITE=1 in the env passed to onionshare-cli so it
   never loads packages from ~/.local — uses only system packages.
2. Install script now directly rm -rf's conflicting packages from
   ~/.local/lib/python3.*/site-packages/ instead of relying on pip3.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 10:14:39 -04:00
thePR0M3TH3ANandClaude Opus 4.6 35069e24c5 fix(install): remove flask/werkzeug pins, clean stale pip packages (v0.2.18)
Previous pip --user installs left werkzeug 2.0.3 in ~/.local which
shadows the system werkzeug and breaks the system onionshare-cli
(Flask 2.2.2 requires werkzeug >= 2.2 for LocalProxy.unbound_message).

Changes:
- Remove flask, werkzeug, and onionshare-cli from voxvera's own
  dependencies — they are onionshare's transitive deps, not ours
- Install script now cleans up stale user-local packages (werkzeug,
  flask, flask-socketio, onionshare-cli, voxvera) before installing
- Prevents pip --user packages from poisoning system Python environment

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 10:06:17 -04:00
thePR0M3TH3ANandClaude Opus 4.6 51ab72239f fix(cli): always use system onionshare-cli binary, even in PyInstaller builds (v0.2.17)
The previous fix only used the system binary for non-frozen (pip/pipx)
installs.  But the PyInstaller binary also needs this — it downloads
successfully but its bundled onionshare_cli import still crashes on
'No module named pkg_resources'.

Now both serve() and _internal_onionshare() unconditionally prefer the
system onionshare-cli/onionshare binary via shutil.which(), falling
back to the Python import only if no system binary exists.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 09:51:44 -04:00
thePR0M3TH3ANandClaude Opus 4.6 4e571b62ea fix(cli): robust onionshare invocation, delete ghost sites, stabilize DATA_DIR (v0.2.16)
Major fixes:
- Use system onionshare-cli binary directly (subprocess/execvp) instead of
  importing it as a Python module, avoiding pkg_resources crashes in pipx venvs
- get_servers() now only searches DATA_DIR/host (single source of truth);
  bundled example sites are seeded on first manage run then fully user-owned
- delete_server() now permanently removes sites (no ghost re-appearance)
- DATA_DIR defaults to ~/  instead of cwd, preventing sites from scattering
  across directories
- Fix attachment URL logic that incorrectly overwrote valid onion URLs

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 09:29:02 -04:00
thePR0M3TH3ANandClaude Opus 4.6 3bb8627d20 fix(install): restrict vendor sys.path and PyInstaller imports to frozen builds only (v0.2.15)
The vendor directory was unconditionally prepended to sys.path, causing
pip/pipx installs to load incomplete vendored setuptools (missing data
files like Lorem ipsum.txt) instead of the system package. Now the
vendor path is only added for PyInstaller frozen builds, and the
force-import block in cli.py is similarly guarded behind sys.frozen.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 09:11:23 -04:00
thePR0M3TH3ANandClaude Opus 4.6 4e84466d38 fix(install): inject setuptools into pipx venv to fix pkg_resources import crash (v0.2.14)
pipx can omit setuptools at runtime even when listed as a dependency,
causing onionshare-cli to crash on `from pkg_resources import resource_filename`.
Add explicit `pipx inject` after install and ensure setuptools is in deps.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 08:41:53 -04:00
thePR0M3TH3AN f530e54f45 fix(build): force collect-all for pkg_resources and setuptools to guarantee inclusion (v0.2.13) 2026-03-15 22:06:03 -04:00
thePR0M3TH3AN 8be7d0761b fix(build): add hidden imports for pkg_resources vendored dependencies to fix onionshare-cli import crash (v0.2.12) 2026-03-15 21:31:02 -04:00
thePR0M3TH3AN 6d747f719f chore: clear release notes after v0.2.11 2026-03-15 21:20:28 -04:00
thePR0M3TH3AN dc83b76317 fix(cli): resolve PyInstaller nano crash, pkg_resources import error, and streamline attachment vs URL logic (v0.2.11) 2026-03-15 21:20:26 -04:00
thePR0M3TH3AN 9c699eb36b fix(cli): correct DATA_DIR paths for export and import, fix test paths (v0.2.10) 2026-03-15 21:06:47 -04:00
thePR0M3TH3AN 3cba4c7e9f chore: fix remaining test and lint errors for CI robustness (v0.2.9) 2026-03-15 20:48:26 -04:00