jevhooks.git / web / README.md
1# Chapter 17: web, the page at hooks.lmjtfy.fun
2
3A plugin that lives in a terminal has a problem when you want to show it to
4someone: they would have to install it first. So jevhooks has one web page.
5It says what the plugin does, shows the four pictures of it doing it, makes
6the plugin's one promise in public, lists every hook point, draws the rules
7that decide, and points at this guide. That is all
8it does, and it is the whole of this folder.
9
10The page is served by a **Cloudflare Worker**: a small program that
11Cloudflare runs on its own machines, near whoever asks, each time a request
12arrives. This one is written in Rust and compiled to WebAssembly. There is
13no server to keep running, no database, and nothing in the browser that
14remembers you.
15
16Almost none of it is new code. The Jev sites (lmjtfy.fun,
17whiskers.lmjtfy.fun, and now this one) kept growing the same parts, so the
18parts were pulled out:
19
20| From | What the site gets |
21| --- | --- |
22| `jev-ui` (`third-party/jevcrates`) | The look: stylesheet, fonts, the page's head, the nav, the windows with black title bars, the pink band, the link-preview tags, the preview card's frame. |
23| `jevhooks-events`, `jevhooks-rules` (`crates/`, chapters 7 and 11) | The plugin's own list of hook events, and its own rules: the page's map and its diagrams are read from the code the plugin runs. |
24| `rete-draw` (`third-party/rustcrates`) | A rules network as a picture. |
25| **Aldebaran** (`third-party/rustcrates`, the `aldebaran-*` crates) | Everything a site's Worker repeats: which address a request came to, the security headers, the request log, the responses, the embedded files. |
26
27What is left for this folder is about a thousand lines: the words, the
28pictures, a few worked cases, and which path gets which answer.
29
30> **Aside: one copy of each picture.** The four pictures are not copied
31> here. They are the same files the chapters show (`plugin/hooks/band.svg`
32> and friends), which `mise run screenshots` takes from a real session
33> (chapter 16), and `content.rs` embeds them from where they are. Retake a
34> picture and the guide and the site change together; they cannot show
35> two different ones.
36
37> **Aside: the card.** Paste a link into Discord and it unfurls into a
38> picture. That picture cannot be an SVG, because the apps that draw link
39> previews do not draw SVG. So the site draws its card as an SVG in Rust
40> (`src/bin/card.rs`, in the shared card look), and the build hands it to
41> `resvg` to make the PNG the page's tags point at. It is made on every
42> build and never committed.
43
44> **Try it.**
45>
46>     mise run test:web      # the page, the gate and the policy, natively, no Cloudflare
47>     mise run web:dev       # http://localhost:8790/
48>
49> Neither needs an account or a login. The tests build the whole page as a
50> string and read it back: that every picture is there with words for
51> someone who cannot see it, that the link preview has every tag Discord
52> reads, that the page has no inline script.
53
54## For the people who maintain it
55
56`hooks.lmjtfy.fun` is the canonical address. Any other address that is not
57local (the account's `workers.dev` one) answers GET and HEAD with a `308`
58to the same path and query there, so a link preview or an old link moves.
59
60Nothing an agent runs here deploys. The owner deploys with
61`mise run web:deploy` (`tools/deploy-web.sh`), which needs
62`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` from fnox or the
63environment, refuses with a message when either is missing, and passes
64`SITE_ORIGIN` (`https://hooks.lmjtfy.fun`).
65
66Measured 2026-10-06 under `wrangler dev`: the page is 107 KB of HTML (ten
67drawings of the rules are about half of it), the four pictures 3.5 to
685.4 KB each, the card 7.7 KB.
69
70### In this folder
71
72| Path | What |
73| --- | --- |
74| [src/](src/) | Chapter 18: the page and the Worker. |
75| [Cargo.toml](Cargo.toml) | A workspace of its own, apart from the plugin's: this builds for wasm32 with the Workers runtime, and the plugin must stay buildable without the Workers toolchain. |
76| [Cargo.lock](Cargo.lock) | Its dependencies at the exact versions built. |
77| [wrangler.toml](wrangler.toml) | The Worker's name, its custom domain, the build command, the assets binding for the card, and the logging rule (no invocation logs). |
78| [.gitignore](.gitignore) | What a build makes: `target/`, `build/`, `public/preview/`, `.wrangler/`. |
79
80← Previous: [Chapter 16, tools/](../tools/) · Up: [jevhooks](../) · Next: [Chapter 18, web/src/](src/) →