Chapter 22: jev-ui — what the Jev sites' pages share
lmjtfy.fun and whiskers.lmjtfy.fun look alike on purpose: near-black on white, one pink band, ordered-dither dot fields and old operating system windows with a black title bar, after typesafe.ai. This crate is the one copy of that look, so the second site did not get a second copy of it.
It is the opposite of its neighbours in one way: nothing here talks to Jev. It is the page around whatever a site says.
What a site gets
| From | What |
|---|---|
asset | The files a page loads, embedded: ui.css, ui.js (the copy buttons and the dither fields), Datastar's datastar.js, the three fonts and a fourth, a small pixel-emoji font. asset::find(path) for a fallback route, Asset::href() for a link named by the hash of its bytes. |
shell | document(head, body_attrs, body): the one head (title, preview tags, preloaded fonts, the shared sheet and script, Datastar) and the body's open and close. Assets::Linked for a site whose policy forbids inline script, Assets::Inline for one that does not. |
components | nav, headline, window, sheet, band, copy_line, footer, skip_link, the 12 by 12 pixel_icon, and SerenityOS's pixel emoji: PixelEmoji (play, pause, the two hourglasses), pixel_emoji and an hourglass that turns over (still under reduced motion). |
preview | Preview: Open Graph and Twitter tags that cannot be built wrong (an origin that is not https, a video of odd size, a card too small to be shown large), for a still card or a looping video, and missing(html, kind) to check a rendered page. |
wire | A call to Jev as a window: json prints a body from the raw text (whitespace added, nothing else), exchange is the folding request-and-response window, bars is lmjtfy's probability rule with an optional decision tick, printed_json reads a panel back for a test. |
flow | spine: a flow drawn as one SVG from rows of steps, each with the ways it can end. Built from the data it is given, so a site that builds the rows from the code that runs the flow cannot draw a step the code lacks. |
card | The preview card's look as SVG: Chrome (the dither, the pink band, the window with its title bar, in lmjtfy's measures), and pixel text. A site composes what goes inside the window. |
A page, in whiskers' shape:
use jev_ui::components::{Link, Nav, band, nav, sheet, window};
use jev_ui::shell::{Assets, Attr, Head, document};
let head = Head { title: "Whiskers", meta: preview_tags, style: SITE_CSS, assets: Assets::Linked,
datastar: true, icon: Some("/favicon.svg"), extra: maud::html! {} };
let page = document(&head, &[Attr::new("data-signals", "{}")], body);
and in the Worker, every asset the crate owns in one route:
// as the router's fallback; `path` and `query` are the request's
if let Some(asset) = jev_ui::asset::find(path) {
// content-type: asset.content_type, cache-control: asset.cache_control(query), body: asset.body
}
What is a parameter and what is not
Everything a site differs in is a field: the title, the tags, the site's own stylesheet (it comes after the shared one), the nav's links, what sits between the name and the links, the footer's words, a picture's query. The look is not a parameter: there is one palette, light only, as typesafe.ai's is. A site that wants another look is not a Jev site.
Fonts are served by the site itself, from this crate (Inter Tight, JetBrains Mono and VT323, all under the SIL Open Font License, see assets/). lmjtfy fetched them from Google Fonts; a Jev site makes no request to another site.
Try it
cargo test -p jev-ui
cargo build -p jev-ui --target wasm32-unknown-unknown
Moving lmjtfy onto it
lmjtfy (~/lmjtfy) still carries its own copy of all of this, which is
temporary debt: apps/lmjtfy/src/page.css and page.js (the shared half of
each is this crate's ui.css and ui.js), view.rs's icon, nav,
footer, clone_line, head and preview, ds-bundle/ (this crate's
datastar.js, byte for byte), the Google Fonts link, and packages/card's
palette, dither and pixel text. To move it:
- Move
third-party/jevcratesto a commit withjev-ui; depend on it by path. - Delete the shared half of
page.cssandpage.js; what remains (transcript, feed, rules diagram, code pages, toasts) is lmjtfy'sHead.styleand its own script. Its.bar-title,.win,.shareand the rest already mean the same thing here. - Replace
view.rs's head and footer withshell::documentandcomponents::footer, withAssets::Inline, and itspreviewwithpreview::Preview(a?q=card isPicture { query: Some("q=…&a=…") }). - Serve
jev_ui::asset::findas a fallback instead of/datastar.js, and drop the Google Fontspreconnectandlink. - Hold lmjtfy's page tests to the same checks, then delete
ds-bundle/.
Outstanding
- Move lmjtfy onto this crate (the steps above), then delete its copies.
-
packages/carddraws lmjtfy's own card straight into a PNG with the same palette, dither and pixel text ascard.rs; once lmjtfy depends on this crate, one of the two should draw from the other. -
lmjtfy's tool-call panels and bars (
view.rs:json,node,choice,jev_card) are the same markup aswire; lmjtfy's.retediagram is Rete-specific and stays its own, but its CSS box language is whatflowborrows. - Mermaid (lmjtfy's code pages) is still fetched from jsdelivr there; it is not part of this crate.
For the people who maintain it
In this folder
| Path | What |
|---|---|
| src/ | The code: Chapter 22½. |
| assets/ | The stylesheet, the script, Datastar and the fonts: Chapter 22¾. |
| Cargo.toml | Dependencies: maud and font8x8 only. |
| CLAUDE.md | Invariants for agents. |
← Previous: Chapter 21½: inside jev-mock/src · Up: jevcrates · Next: Chapter 22½: inside jev-ui/src →