What the page says and shows, as data: the words a link preview carries, the four pictures, and
the addresses it links to. No markup here; view draws it.
7pub const NAME: &str = "jevhooks"; 8pub const TITLE: &str = "jevhooks: Jev judges what Claude Code is about to do"; 9pub const DESCRIPTION: &str = "A Claude Code plugin. Before a shell command runs, Jev says what kind of act it is, how hard it \ 10 would be to undo and how much memory it needs. Ordinary work runs without a prompt, risky work is put to you, and \ 11 heavy commands wait their turn.";
Where the site is when nothing says otherwise (wrangler dev). The deploy passes the real one.
14pub const SITE_ORIGIN: &str = "http://localhost:8790";
The guide: every folder of the repository as a chapter, on the code host.
17pub const GUIDE: &str = "https://code.lmjtfy.fun/jevhooks.git/"; 18pub const CHAPTER_HOOKS: &str = "https://code.lmjtfy.fun/jevhooks.git/plugin/hooks/"; 19pub const CHAPTER_QUESTIONS: &str = "https://code.lmjtfy.fun/jevhooks.git/crates/jevhooks-rules/src/"; 20pub const CHAPTER_DAEMON: &str = "https://code.lmjtfy.fun/jevhooks.git/crates/jevhooks-daemon/"; 21pub const JEV_DOCS: &str = "https://docs.typesafe.ai"; 22pub const LMJTFY: &str = "https://lmjtfy.fun";
What a stranger types to see it work, with no Rust, no key and no account.
25pub const TRY_IT: &str = "git clone --recurse-submodules https://lmjtfy.fun/jevhooks.git && cd jevhooks && claude plugin test plugin";
The release the page tells people to install: the workspace's version (tools/check-versions.sh
holds this to the root Cargo.toml).
29pub const VERSION: &str = "0.1.0";
The public half of the key releases are signed with: the second line of tools/release.pub,
the one copy of it. Whoever installs gives it to mise, which then accepts only what it signed.
The name releases are signed under, which is where they are served.
38pub const PROJECT: &str = "code.lmjtfy.fun/jevhooks";
Installs the plugin, daemon built in, with mise checking the signature.
Starts a session with the installed plugin loaded.
46pub const RUN_IT: &str = "claude --plugin-dir \"$(mise where packslip:code.lmjtfy.fun/jevhooks)\"";
The same files for someone without mise: the archive for this machine and the digests to check it by.
49pub const BY_HAND: &str = "https://code.lmjtfy.fun/jevhooks/latest/";
The link preview's card: drawn by src/bin/card.rs, made a PNG by tools/build-web.sh.
A hook, 12 by 12, # for ink: the title bars' and the tab's icon.
One picture of the real terminal, and what it shows.
The window's title.
68 pub title: &'static str,
For someone who cannot see the picture: what is on the screen.
70 pub alt: &'static str,
One or two plain sentences beside it.
72 pub says: &'static str,
The picture's own size, so the page does not jump as it loads.
78const SVG: &str = "image/svg+xml";
The pictures are the guide's own: mise run screenshots takes them from a real session and writes
them beside the chapters that show them. They are embedded from there, so the site cannot show a
different picture from the guide.
83pub const BAND: Shot = Shot { 84 asset: Asset { path: "/shots/band.svg", content_type: SVG, body: include_bytes!("../../plugin/hooks/band.svg") }, 85 title: "Ordinary work just runs", 86 alt: "Claude Code's terminal after a command ran with no prompt. Above the prompt, a line reads: command: Jev: builds or tests; undone by one command; allowed.", 87 says: "Reading, building and editing run without a prompt. A band above the prompt says what Jev made of the last \ 88 command, how long it took and what the session's questions have cost.", 89 width: 1048, 90 height: 268, 91};
93pub const ASK: Shot = Shot { 94 asset: Asset { path: "/shots/ask.svg", content_type: SVG, body: include_bytes!("../../plugin/hooks/ask.svg") }, 95 title: "Anything hard to undo is put to you", 96 alt: "Claude Code's permission dialog for git reset --hard and git clean. It carries Jev's reason: discards or rewrites version-control state; hard to undo.", 97 says: "A command that deletes, rewrites history, changes the machine, sends something elsewhere or runs code nobody \ 98 has read stops at a prompt, with Jev's reason in it. You decide.", 99 width: 1048, 100 height: 436, 101}; 102 103pub const WAITING: Shot = Shot { 104 asset: Asset { path: "/shots/waiting.svg", content_type: SVG, body: include_bytes!("../../crates/jevhooks-daemon/src/waiting.svg") }, 105 title: "Heavy commands wait their turn", 106 alt: "Claude Code's terminal while cargo build --release waits. The band reads: waiting for memory: needs 3.0 GB, 1.0 GB to spare.", 107 says: "Every session on the machine shares one ledger of the memory already promised to commands. Five agents \ 108 starting five builds in the same second get two builds now and three in a queue, where they used to get a \ 109 dead machine.", 110 width: 1048, 111 height: 268, 112}; 113 114pub const MODEL: Shot = Shot { 115 asset: Asset { path: "/shots/model.svg", content_type: SVG, body: include_bytes!("../../crates/jevhooks-daemon/src/model.svg") }, 116 title: "A prompt goes to the cheapest model that will do", 117 alt: "Claude Code's terminal after the prompt: What branch am I on? The band reads: prompt: Jev: a lookup; haiku.", 118 says: "Switch it on and Jev scores what each prompt takes, on four levels from a lookup to open-ended work. The turn \ 119 goes to the cheapest of haiku, sonnet, opus and fable that is 75% likely to be enough.", 120 width: 1048, 121 height: 218, 122};
In the order the page shows them.
125pub const SHOTS: [Shot; 4] = [BAND, ASK, WAITING, MODEL];
Every file the site serves from itself besides the shared look's.
128pub static FILES: Bundle = Bundle::new(&[BAND.asset, ASK.asset, WAITING.asset, MODEL.asset]);
A picture's declared size is what keeps the page from jumping as it loads, so it has to be the picture's real one: read back from the SVG the screenshots task wrote.
136 #[test] 137 fn each_picture_declares_its_real_size() { 138 for shot in SHOTS { 139 let svg = std::str::from_utf8(shot.asset.body).unwrap(); 140 let number = |name: &str| -> f64 { 141 let at = svg.find(&format!("{name}=\"")).unwrap_or_else(|| panic!("{}: no {name}", shot.asset.path)) + name.len() + 2; 142 svg[at..].split('"').next().unwrap().parse().unwrap() 143 }; 144 assert_eq!((number("width").round() as u32, number("height").round() as u32), (shot.width, shot.height), "{}", shot.asset.path); 145 } 146 }
The screenshots task drops the rows Claude Code draws about the account of whoever ran it. This is the last place one could get out, so it is checked here too.
150 #[test] 151 fn no_picture_shows_the_account_of_whoever_took_it() { 152 for shot in SHOTS { 153 let svg = std::str::from_utf8(shot.asset.body).unwrap(); 154 for private in ["weekly limit", "usage-credits", "/home/", "/Users/"] { 155 assert!(!svg.contains(private), "{} contains {private:?}", shot.asset.path); 156 } 157 } 158 }