diff --git a/content/getting-started.mdx b/content/getting-started.mdx
index ba94b54..9f8cd5a 100644
--- a/content/getting-started.mdx
+++ b/content/getting-started.mdx
@@ -1,14 +1,14 @@
# Getting Started
`zig-nostr` is a Nostr protocol library for [Zig](https://ziglang.org). It
-targets Zig **0.16** and is **early** (`v0.3.8`).
+targets Zig **0.16** and is in **active development** (`v0.12.0`).
## Install
Fetch the library into your project's `build.zig.zon`:
```sh
-zig fetch --save https://github.com/zig-nostr/nostr/archive/refs/tags/v0.3.8.tar.gz
+zig fetch --save https://github.com/zig-nostr/nostr/archive/refs/tags/v0.12.0.tar.gz
```
Then wire the module in `build.zig`:
diff --git a/content/index.mdx b/content/index.mdx
index 581a2e8..c195b0c 100644
--- a/content/index.mdx
+++ b/content/index.mdx
@@ -20,7 +20,7 @@ title: The Nostr protocol, natively in Zig
0.28 ms
500-note feed query @ 100k events
-
+
19/19
BIP-340 test vectors
@@ -116,7 +116,7 @@ title: The Nostr protocol, natively in Zig
```sh
-zig fetch --save https://github.com/zig-nostr/nostr/archive/refs/tags/v0.3.8.tar.gz
+zig fetch --save https://github.com/zig-nostr/nostr/archive/refs/tags/v0.12.0.tar.gz
```
```zig
diff --git a/content/nips.mdx b/content/nips.mdx
index 7989ba6..64a9cc6 100644
--- a/content/nips.mdx
+++ b/content/nips.mdx
@@ -43,8 +43,8 @@ rather than library surface.
## Planned, and where it lands
These are the specs the [roadmap](/roadmap) commits to, in roadmap order, each
-against the milestone that delivers it. None of it is written yet, which is what
-the status column says on every row. A spec named here is a promise: if one is
+against the milestone that delivers it. Three rows have a half already written, and say so;
+the rest are untouched. A spec named here is a promise: if one is
missing, it is because I have not made that promise, not because I forgot it.
| Spec | Title | Milestone | Status |
diff --git a/content/plaza.mdx b/content/plaza.mdx
index f1fb1cc..944c4da 100644
--- a/content/plaza.mdx
+++ b/content/plaza.mdx
@@ -85,7 +85,7 @@ Followers is not: nothing on your disk can know who follows a person, and the
honest options are an indexer's figure or none. Plaza does not print numbers it
cannot check.
-Private messages are a milestone away.
+Private messages are two milestones out on the [roadmap](/roadmap).
## Where the feed comes from
@@ -159,18 +159,20 @@ than an app that does nothing at all.
The feed is a windowed list: it builds only the rows near the viewport, so its
cost follows the window rather than the length of the feed. Measured on the
-ReleaseFast build while scrolling hard through a live feed:
+build that ships (ReleaseFast), scrolling hard through a fixed feed of 240
+notes the harness seeds into a store of its own, so a run means the same thing
+twice:
| Stage | p90 | Budget |
| --- | --- | --- |
-| Rebuild | 54µs | 400µs |
-| Layout | 432µs | 1500µs |
-| Patch | 19µs | 200µs |
-
-A 120 Hz frame is 8333µs, so a hard scroll spends about a tenth of
-one. Sixty notes mount 63 widget nodes rather than roughly 500. The numbers are
-reproducible: `scripts/frame-budget.sh` in the repo measures them and fails on a
-regression.
+| Rebuild | 275µs | 600µs |
+| Layout | 1360µs | 2200µs |
+| Patch | 55µs | 150µs |
+
+A 120 Hz frame is 8333µs, so a hard scroll spends about a quarter of
+one. A long feed mounts around 460 widget nodes rather than one per note. The
+numbers are reproducible: `scripts/frame-budget.sh` in the repo measures them
+and fails on a regression.
See [performance](/performance) for the library numbers underneath.
diff --git a/content/roadmap.mdx b/content/roadmap.mdx
index 523e276..5c66643 100644
--- a/content/roadmap.mdx
+++ b/content/roadmap.mdx
@@ -52,7 +52,7 @@ Then the ordinary parts. Pictures, which is one absence with three symptoms: no
photo, no avatar, no banner. Uploading is [Blossom](https://github.com/hzrd149/blossom)
rather than a NIP: BUD-02 `PUT /upload`, authorised by a signed kind 24242 event (BUD-11),
aimed at the servers in your kind 10063 list (BUD-03), with a NIP-92 `imeta` tag so they
-render elsewhere. A profile with more than three fields, because Plaza reads eight out of a
+render elsewhere. A profile with more than three fields, because Plaza reads nine out of a
kind 0 and lets you edit three, and `lud16` is not one of them, so an account set up only in
Plaza cannot receive a zap at all. Zaps you can send, not only verify. Finding a person, both
locally over the profiles already on your disk and through NIP-50 where a relay supports it.
diff --git a/content/signer.mdx b/content/signer.mdx
index edfcae2..55462b4 100644
--- a/content/signer.mdx
+++ b/content/signer.mdx
@@ -15,7 +15,7 @@ before it happens, and the key never leaves the daemon.
## What it does
@@ -32,8 +32,10 @@ before it happens, and the key never leaves the daemon.
- A headless **daemon** holds the key (encrypted at rest with NIP-49) and speaks
NIP-46 over relays.
- A native **approval GUI** shows each incoming request (`sign_event`,
- `nip44_encrypt`, and so on) and forwards your approve/deny decision over a
- loopback channel. The GUI never sees the key.
+ `nip44_encrypt`, and so on) and forwards your answer over a loopback channel:
+ allow once, for a day, always, or deny. The key is generated and decrypted in
+ the daemon; the GUI sends it a passphrase, and an nsec only when you import an
+ existing key on the setup screen.
- Clients connect with a `bunker://` URL. It works end-to-end over public
relays, including those that require NIP-42 authentication.
@@ -49,10 +51,15 @@ The installer resolves the latest release, verifies its SHA-256, installs
## Supported operations
-`sign_event` · `get_public_key` · `nip44_encrypt` · `nip44_decrypt` · `ping`
+`connect` · `sign_event` · `ping` · `get_public_key` · `nip44_encrypt` · `nip44_decrypt` · `logout`
-Each is gated by a per-request approval policy with method and event-kind
-allowlists, so you decide exactly what a connected client may ask for.
+Only the three that touch the key reach you; `connect`, `ping`,
+`get_public_key` and `logout` are bookkeeping and are answered without waking
+anybody. A static allowlist of methods and event kinds is the first gate, and
+your answer carries a duration: once, for a day, or always, keyed by client,
+method and event kind, so a client that signs in a loop is not a prompt in a
+loop. A refusal stands for an hour, and a request nobody answers is not written
+down at all.
## Why it matters
diff --git a/public/plaza/panel-places.jpg b/public/plaza/panel-places.jpg
index 0027394..907a0b5 100644
Binary files a/public/plaza/panel-places.jpg and b/public/plaza/panel-places.jpg differ