3.5 KiB
VoxVera Nostr Static Client Spec
Status: main is the Nostr static-client branch. The legacy Tor/OnionShare CLI version is preserved on legacy-tor.
Goal
VoxVera should let anyone publish, fetch, and print flyer content without a centralized application server.
The static client is responsible for:
- rendering the flyer
- generating QR codes
- authoring a normalized Nostr flyer payload
- generating anonymous browser-side Nostr keys
- signing and publishing replaceable Nostr events
- fetching events by raw event ID,
note1,nevent1, ornaddr - rendering fetched content safely
Nostr relays store the flyer source content. Static hosts only serve the client.
Active Files
site/
index.html
CNAME
nostr/
index.html
nostr-client.css
nostr-client.js
locales.js
vendor/
nostr-tools/
qrcode-generator/
voxvera/
nostr/
schema.py
tests/
test_nostr_schema.py
Deployment
The deployable static root is site.
No build command is required. Vercel should use:
- root directory: repository root
- output directory:
site - build command: empty
site/index.html redirects root traffic to /nostr/.
Event Model
Recommended event kind: 30078 parameterized replaceable event.
Required tags:
["d", "voxvera:<slug>"]["t", "voxvera"]["t", "flyer"]
Payload:
{
"type": "voxvera_flyer",
"version": 1,
"folder_name": "voxvera",
"name": "Vox Vera Printable Flyers",
"lang": "en",
"title": "TOP SECRET",
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
"headline": "OPERATION VOX VERA",
"content": "Flyer body text",
"url_message": "Follow this link to learn more.",
"url": "https://creator.example/action",
"tear_off_link": "https://voxvera.org/nostr/?addr=naddr1...",
"footer_message": "0110010",
"qr_target": "flyer_url"
}
The UI labels folder_name as Flyer Name; it acts as the event slug, not a local hosting folder.
QR Target
Default behavior: qr_target: "flyer_url".
url is always the creator-controlled content destination shown in the body of the flyer. The editor must not overwrite this field with generated app URLs.
When publishing, the client computes the replaceable-event naddr and writes the generated poster URL to tear_off_link. The tear-off tabs and tear-off QR code point at tear_off_link, so people can re-open and reprint the flyer. The main flyer QR code points at url, so viewers can visit the creator's intended destination.
Supported values:
flyer_url: tear-off QR points to the hosted client URL for the postercontent_url: QR points to the payload's content URLnostr_event: QR points to the Nostr source identifier when available
Safety
Nostr content is untrusted.
The client and schema helper must:
- reject raw HTML in flyer text fields
- escape rendered flyer content
- reject unsafe URL schemes such as
javascript:anddata: - keep attachments unsupported in V1
- avoid remote frontend dependencies
Localization
Flyer defaults come from site/nostr/locales.js.
Nostr editor/viewer controls and Nostr-specific flyer labels come from NOSTR_UI in site/nostr/nostr-client.js.
Language changes should:
- update default flyer content while the flyer is still using default content
- preserve user-authored or fetched event content
- update editor/viewer tool labels
- update printed side-tab labels
- respect RTL direction metadata
Verification
node --check site/nostr/nostr-client.js
pytest -q
For UI work, also verify browser rendering and print output from a local static server.