The page as a pure function: data in, HTML out. Nothing here fetches, reads the clock or knows a visitor, so the whole page is tested as a string, natively.
The look is jev-ui's, the same as lmjtfy.fun's and whiskers.lmjtfy.fun's: the shell, the nav,
the windows, the band. What is here is jevhooks': the words and the four pictures.
12use crate::hooks::{self, Phase, State}; 13use crate::rules; 14use crate::content::{ 15 CARD_ALT, CARD_HEIGHT, CARD_PATH, CARD_WIDTH, CHAPTER_DAEMON, CHAPTER_HOOKS, CHAPTER_QUESTIONS, DESCRIPTION, GUIDE, HOOK, JEV_DOCS, LMJTFY, NAME, SHOTS, 16 BY_HAND, RUN_IT, Shot, TITLE, TRY_IT, VERSION, install, 17}; 18 19const CSS: &str = include_str!("page.css");
The bar down the side of a Discord embed: the page's pink, as lmjtfy's.
22const THEME: &str = "#f386a1";
The hook as a still picture, for the browser tab.
25pub fn favicon() -> String { 26 let mut svg = String::from("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 12 12\" shape-rendering=\"crispEdges\">"); 27 for (y, row) in HOOK.iter().enumerate() { 28 for (x, cell) in row.bytes().enumerate() { 29 if cell == b'#' { 30 svg.push_str(&format!("<rect x=\"{x}\" y=\"{y}\" width=\"1\" height=\"1\" fill=\"#1e1e1e\"/>")); 31 } 32 } 33 } 34 svg.push_str("</svg>"); 35 svg 36}
38fn preview(origin: &Origin) -> Preview<'_> { 39 Preview { 40 origin, 41 site_name: NAME, 42 title: TITLE, 43 description: DESCRIPTION, 44 page: "/", 45 picture: Picture { path: CARD_PATH, query: None, width: CARD_WIDTH, height: CARD_HEIGHT, alt: CARD_ALT }, 46 video: None, 47 theme_color: THEME, 48 } 49}
One feature: a window with the sentence and the picture of it happening.
52fn feature(shot: &Shot, eager: bool) -> Markup { 53 window( 54 Some(&HOOK), 55 html! { h2 { (shot.title) } }, 56 None, 57 true, 58 html! { 59 p { (shot.says) } 60 figure .shot { 61 img src=(shot.asset.href()) alt=(shot.alt) width=(shot.width) height=(shot.height) loading=(if eager { "eager" } else { "lazy" }) decoding="async"; 62 } 63 }, 64 ) 65}
The chip that says what is done with an event.
Every hook point and what jevhooks does there, grouped by where in a session it is raised.
73fn map() -> Markup { 74 let hooks = hooks::all(); 75 html! { 76 div #hooks .stack { 77 (window(Some(&HOOK), html! { h2 { "Every hook point, and what is done there" } }, None, true, html! { 78 p { 79 "Claude Code raises " (hooks.len()) " kinds of event that a plugin can answer. jevhooks is built at " 80 (hooks::count(State::Judged) + hooks::count(State::Heard)) " of them. This is all of them, so you can see what is built, what is only an idea, and what nobody has thought about yet." 81 } 82 ul .legend { 83 @let (events, ideas) = hooks::ideas(); 84 @for state in State::ALL { 85 li { 86 (chip(state)) 87 @if state == State::Idea { 88 span { (ideas) " for " (events) " of the events. " (state.means()) } 89 } @else { 90 span { (hooks::count(state)) " events. " (state.means()) } 91 } 92 } 93 } 94 } 95 })) 96 @for phase in Phase::ALL { 97 (window(Some(&HOOK), html! { h3 { (phase.title()) } }, None, true, html! { 98 ul .hooks { 99 @for hook in hooks.iter().filter(|hook| hook.phase == phase) { 100 li { 101 div .hook-name { code { (hook.name()) } (chip(hook.state())) } 102 @if !hook.today.is_empty() { p { (hook.today) } } 103 @if !hook.ideas.is_empty() { 104 p .ideas-title { (chip(State::Idea)) " written down for it:" } 105 ul .ideas { @for idea in hook.ideas { li { (idea) } } } 106 } 107 } 108 } 109 } 110 })) 111 } 112 (window(Some(&HOOK), html! { h3 { "Two more, outside that list" } }, None, true, html! { 113 p { "The plugin also steps in at two points of Claude Code's newer plugin interface, to choose a model. Both are built, and off unless you switch them on." } 114 ul .hooks { 115 @for (name, does) in hooks::BEYOND { 116 li { div .hook-name { code { (name) } (chip(State::Judged)) } p { (does) } } 117 } 118 } 119 })) 120 } 121 } 122}
The rules that turn Jev's answers into what happens, drawn from the networks that run.
125fn rulebook() -> Markup { 126 html! { 127 div #rules .stack { 128 (window(Some(&HOOK), html! { h2 { "The rules, drawn from the rules that run" } }, None, true, html! { 129 p { 130 "Jev answers with numbers. What happens next is decided by a few rules, in order: of the rules that hold, the first decides. " 131 "They are compiled into one small network, and these pictures are drawn from that network, not beside it, so they cannot show a rule the plugin does not have." 132 } 133 p { 134 "Read one left to right: what can be known, the tests on it, where a rule's tests meet, and the rules. " 135 "Black is what a rule that holds stands on, and dashed is a test that failed. A pink rule holds; more than one can, and the numbered ones are those that decided, in order. " 136 "The framed facts are Jev's, and are asked for together, in one request. Open a case to see how it went." 137 } 138 })) 139 @for judgment in rules::judgments() { 140 (window(Some(&HOOK), html! { h3 { (judgment.title) } }, None, true, html! { 141 p { (judgment.says) } 142 (judgment.cases) 143 })) 144 } 145 } 146 } 147}
The page. origin is where the site is served, for the link preview's absolute addresses.
150pub fn page(origin: &Origin) -> Markup { 151 // The tags cannot be built wrong (`jev_ui::preview`), and these are constants a test builds, so 152 // a failure here is a programming error the tests catch, not a visitor's. 153 let meta = preview(origin).html().expect("the preview's constants are valid (the_page_carries_a_whole_link_preview)"); 154 let head = Head { title: TITLE, meta: PreEscaped(meta), style: CSS, assets: Assets::Linked, datastar: false, icon: Some("/favicon.svg"), extra: html! {} }; 155 let links = [Link { href: "#hooks", label: "Hooks" }, Link { href: "#rules", label: "Rules" }, Link { href: "#install", label: "Install" }, Link { href: GUIDE, label: "The guide" }, Link { href: LMJTFY, label: "lmjtfy" }]; 156 let body = html! { 157 (skip_link("main", "Skip to the page")) 158 (nav(&Nav { label: "Site", brand: Link { href: "/", label: NAME }, middle: html! {}, links: &links })) 159 main #main { 160 (sheet(html! { 161 (headline(Some("A Claude Code plugin"), "Jev judges what Claude Code is about to do.")) 162 p .lede { 163 "Claude Code runs shell commands for you and decides for itself when it has finished. jevhooks stops at " 164 "those moments to ask a second opinion of something that does not write, cannot be talked round, and " 165 "costs a few thousandths of a cent to ask." 166 } 167 p { 168 "That something is " a href=(JEV_DOCS) { "Jev" } ", TypeSafe AI's System One model. It writes no text. It is " 169 "handed a situation and a question whose answers are already written down, and says how likely each " 170 "answer is, in about a quarter of a second." 171 } 172 })) 173 (sheet(html! { 174 div .stack { 175 @for (index, shot) in SHOTS.iter().enumerate() { (feature(shot, index == 0)) } 176 } 177 p .small { "Every picture is a real session, taken by a script in the repository. Nothing is drawn by hand." } 178 })) 179 (band(html! { 180 p .promise { "Jev being slow, absent, unsure or broken never blocks anything. Then whatever would have happened without the plugin happens." } 181 })) 182 (sheet(html! { div .below-band { (map()) } })) 183 (sheet(html! { (rulebook()) })) 184 (sheet(html! { div .stack { 185 (window(Some(&HOOK), html! { h2 { "How it works" } }, None, true, html! { 186 ol .steps { 187 li { "A small piece of the plugin hears every event Claude Code raises. A command that plainly only reads goes no further: no question, no cost. (" a href=(CHAPTER_HOOKS) { "the hooks" } ")" } 188 li { "Anything worth judging goes to one daemon for the whole machine, which keeps the connection to Jev warm and the key in one place. (" a href=(CHAPTER_DAEMON) { "the daemon" } ")" } 189 li { "The daemon asks Jev three things in one request: which of eight kinds of act this is, how hard it would be to put back, and how much of the machine it takes. (" a href=(CHAPTER_QUESTIONS) { "the questions" } ")" } 190 li { "Rules written in code, where a test can hold them still, turn the answers into allow, ask, wait or no opinion. Your own hooks still run, and the stricter answer wins." } 191 } 192 })) 193 (window(Some(&HOOK), html! { h2 #install { "Install it" } }, None, true, html! { 194 p { "Version " (VERSION) ", for Linux on x86_64 and arm64: the plugin with its daemon already built, so it needs no Rust. With " a href="https://mise.jdx.dev" { "mise" } ":" } 195 (copy_line("install-line", html! { "Install" }, &install())) 196 (copy_line("run-line", html! { "Run" }, RUN_IT)) 197 p .small { 198 "The long word in the first line is the public half of the key every release is signed with. mise checks the download against it and refuses anything else. " 199 "Without mise, the same files and their SHA-256 digests are " a href=(BY_HAND) { "here" } ": unpack one and give its folder to " code { "claude --plugin-dir" } "." 200 } 201 p .small { "To judge anything the daemon needs a TypeSafe key in " code { "TYPESAFE_API_KEY" } ". Without one it runs, asks nothing, and changes nothing." } 202 })) 203 (window(Some(&HOOK), html! { h2 { "Try it" } }, None, true, html! { 204 p { "Not ready to install? With Claude Code installed, this only runs the plugin's tests against a stand-in for the daemon, to show it working. It installs nothing, and needs no Rust, no Jev key and no account." } 205 (copy_line("try", html! { "Run" }, TRY_IT)) 206 p .small { "The whole repository is written as " a href=(GUIDE) { "a guide in chapters" } ": every folder says what it is and why." } 207 })) 208 } })) 209 } 210 (footer( 211 &HOOK, 212 html! { "jevhooks is a personal project, not a product. Jev is TypeSafe AI's; this plugin is not theirs." }, 213 Some(html! { a href=(GUIDE) { "Read the code" } }), 214 )) 215 // An icon nobody sees, so the shared sheet's `.icon` rule is exercised on every page the same way. 216 span hidden { (pixel_icon(&HOOK, "")) } 217 }; 218 document(&head, &[], body) 219}
A link pasted into Discord unfurls only when every tag it reads is there; the shared crate knows the list, and the page is checked against it as rendered.
Each feature is shown, not only described: all four pictures, each with words for someone who cannot see it and a declared size so the page does not jump.
241 #[test] 242 fn every_feature_has_its_picture() { 243 let html = rendered(); 244 for shot in SHOTS { 245 assert!(html.contains(&shot.asset.href()), "{}", shot.asset.path); 246 assert!(html.contains(shot.title), "{}", shot.title); 247 assert!(!shot.alt.is_empty()); 248 } 249 assert_eq!(html.matches("<img ").count(), SHOTS.len()); 250 assert_eq!(html.matches("loading=\"eager\"").count(), 1, "only the first picture is fetched at once"); 251 }
The policy forbids inline script (headers::policy), so the page must not have any: one
would be silently dropped by the browser.
Windows that follow one another sit in a .stack, which is what puts space between
them: two windows placed straight into a sheet touch (seen on the first deploy, where
"How it works" ran into "Try it" and into the band above).
267 #[test] 268 fn no_window_touches_the_next() { 269 let html = rendered(); 270 // The pictures, the map (its head, a window a phase, the two beyond), the rules (their 271 // head and a window a judgment), then how and try. 272 assert_eq!(html.matches("class=\"win").count(), SHOTS.len() + 1 + Phase::ALL.len() + 1 + 1 + rules::judgments().len() + 3); 273 assert_eq!(html.matches("class=\"stack").count(), 4, "the pictures, the map, the rules, and the last three windows"); 274 // Every window is inside one of the two: a window opens only after a stack has. 275 let first_window = html.find("class=\"win").unwrap(); 276 assert!(html.find("class=\"stack").unwrap() < first_window); 277 let below = html.find("class=\"below-band").unwrap(); 278 assert!(html[below..].find("class=\"stack").unwrap() < html[below..].find("class=\"win").unwrap(), "what follows the band is in a stack"); 279 }
The map names every event, each once as a heading of its own row, and says of each what is done there in one of the four words.
283 #[test] 284 fn the_map_shows_every_event_with_its_state() { 285 let html = rendered(); 286 for hook in hooks::all() { 287 let row = format!("<code>{}</code><span class=\"chip {}\">", hook.name(), hook.state().class()); 288 assert_eq!(html.matches(&row).count(), 1, "{}", hook.name()); 289 } 290 assert!(html.contains("jevhooks is built at 7 of them"), "the count is the plugin's own"); 291 }
The page is the plugin's promise in public, so it has to make it. The rules are on the page as drawings of the networks, one per worked case, and each case can be opened without script.
296 #[test] 297 fn the_rules_are_drawn_on_the_page() { 298 let html = rendered(); 299 assert_eq!(html.matches("<div class=\"rete\">").count(), 10); 300 assert_eq!(html.matches("<details class=\"case\"").count(), 10); 301 assert_eq!(html.matches("<details class=\"case\" open").count(), 3, "the first case of each judgment is open"); 302 assert!(html.contains("href=\"#rules\"")); 303 }
The page says how to install the release: the signed route with the key that is in the repository, the exact version, and the files for someone without mise.
307 #[test] 308 fn the_page_says_how_to_install() { 309 let html = rendered(); 310 let key = crate::content::release_key(); 311 assert!(key.starts_with("RW") && key.len() == 56, "a minisign public key line: {key}"); 312 assert!(html.contains(&format!("pubkey={key},allow_unlogged=true]@{VERSION}"))); 313 assert!(html.contains("mise where packslip:code.lmjtfy.fun/jevhooks")); 314 assert!(html.contains(BY_HAND)); 315 }
317 #[test] 318 fn the_page_makes_the_promise() { 319 assert!(rendered().contains("never blocks anything")); 320 } 321 322 #[test] 323 fn the_favicon_is_the_hook() { 324 let ink = HOOK.iter().map(|row| row.bytes().filter(|&cell| cell == b'#').count()).sum::<usize>(); 325 assert_eq!(favicon().matches("<rect").count(), ink); 326 } 327}