Files
VoxVera/site/board.html
T
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

103 lines
5.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VoxVera — Bulletin Board</title>
<!-- Root-absolute asset paths: with Vercel cleanUrls + trailingSlash this
page is served at /board/, so relative paths would resolve to
/board/... and 404. -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/nostr-client.css">
</head>
<body class="board-page">
<header class="topbar">
<div>
<h1 id="board-title">Bulletin Board</h1>
<p id="board-subtitle">Most recent flyers published to the relays.</p>
</div>
<div class="topbar-controls">
<a id="board-back" class="board-back" href="/">Back to VoxVera</a>
<span id="board-identity" class="board-identity" hidden>
<span id="board-identity-npub" class="board-npub"></span>
<button id="board-disconnect" type="button" class="board-disconnect">Disconnect</button>
</span>
<select id="board-lang" aria-label="Language"></select>
</div>
</header>
<main class="board-main">
<!-- Login gate: the board is visible only to a connected Nostr identity.
The data itself is public on the relays, so this is not access
control — it supplies the viewer's identity so a later web-of-trust
filter can show them a trustworthy board (see docs/roadmap.md). -->
<section id="board-gate" class="board-gate">
<h2 id="board-gate-title">Connect to view the board</h2>
<p id="board-gate-body">Connect a Nostr identity to view the bulletin board.</p>
<div class="board-connect-methods">
<button id="board-connect" type="button" class="board-connect">Browser extension (NIP-07)</button>
<button id="board-connect-nsec-toggle" type="button" class="board-connect-alt">Use a private key (nsec)</button>
<button id="board-generate" type="button" class="board-connect-alt">Create a new key</button>
</div>
<!-- nsec import: the key is decoded in-page to derive the pubkey, then
discarded. Only the pubkey is persisted (board never signs). -->
<div id="board-nsec-row" class="board-nsec-row" hidden>
<input id="board-nsec-input" type="password" autocomplete="off" spellcheck="false" placeholder="nsec1…">
<button id="board-nsec-submit" type="button" class="board-connect">Connect</button>
<p id="board-nsec-note" class="board-key-note"></p>
</div>
<!-- Generated key reveal: shown once so the user can save the secret. -->
<div id="board-generated" class="board-generated" hidden>
<p id="board-gen-warning" class="board-key-warning"></p>
<label class="board-key-field">npub
<input id="board-gen-npub" readonly>
</label>
<label class="board-key-field">nsec
<span class="board-key-secret">
<input id="board-gen-nsec" type="password" readonly>
<button id="board-gen-copy" type="button" class="board-disconnect">Copy</button>
</span>
</label>
<button id="board-gen-continue" type="button" class="board-connect">Continue</button>
</div>
<p id="board-gate-error" class="board-gate-error" role="alert" hidden></p>
</section>
<div id="board-content" hidden>
<div class="board-filter">
<span id="board-filter-note" class="board-filter-note"></span>
<span id="board-blocked-note" class="board-blocked-note" hidden>
<span id="board-blocked-count">0</span>&nbsp;·&nbsp;<button id="board-unblock-all" type="button" class="board-action">Clear blocked</button>
</span>
<label class="board-show-all">
<input type="checkbox" id="board-mine-only">
<span id="board-mine-only-label">My flyers</span>
</label>
<label class="board-show-all">
<input type="checkbox" id="board-show-all">
<span id="board-show-all-label">Show all flyers</span>
</label>
</div>
<p id="board-status">Loading…</p>
<table class="board-table" id="board-table">
<thead><tr id="board-head"></tr></thead>
<tbody id="board-rows"></tbody>
</table>
</div>
</main>
<div id="board-delete-modal" class="modal-overlay" hidden>
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="board-delete-title">
<h2 id="board-delete-title">Delete this flyer?</h2>
<p id="board-delete-message">This requests deletion from the relays and hides it in VoxVera. Deletion can't be guaranteed across every relay.</p>
<div class="modal-actions">
<button id="board-delete-cancel" type="button" class="board-action">Cancel</button>
<button id="board-delete-confirm" type="button" class="board-action danger">Delete</button>
</div>
</div>
</div>
<script src="/locales.js"></script>
<script src="/vendor/nostr-tools/nostr.bundle.js"></script>
<script src="/board.js"></script>
</body>
</html>