The code pages: /<repo>.git, a repository's front page, and
/<repo>.git/<path>, a directory or a file in it, for each repository
the site serves. Pure: what GitHub said in, markup out.
5use maud::{DOCTYPE, Markup, PreEscaped, html};
What a path came to, to show.
11pub enum Shown<'a> {
A folder, and its documents.
Which tab of the main pane is open: a folder's README (for people) or CLAUDE.md (for agents, on top of the README); a markdown file rendered or as its source.
29impl View {
From ?view=: agents, source, or anything else for people.
A document, rendered, with where it is served.
A CLAUDE.md's @README.md, as the served path it imports.
45 pub imports: Option<String>,
50impl Doc {
text from the file at path (served), rendered for its kind: a
CLAUDE.md shows its import as a link rather than as a line.
base is the repository's address, /lmjtfy.git, that its relative
links are made under.
55 pub fn new(path: &str, text: &str, base: &str) -> Doc { 56 let dir = tree::parent(path); 57 let name = path.rsplit('/').next().unwrap_or(path); 58 let (imports, text) = 59 if name.eq_ignore_ascii_case("CLAUDE.md") { tree::agents(text) } else { (None, text) }; 60 let imports = imports.map(|imported| if dir.is_empty() { imported.to_owned() } else { format!("{dir}/{imported}") }); 61 Doc { path: path.to_owned(), rendered: tree::markdown(text, dir, base), imports, guide: tree::guide(text, dir, base) } 62 } 63}
A folder's README and CLAUDE.md, and which is shown.
A repository the site serves, as the code pages list it.
lmjtfy.git: its address under the site, and its name here.
85 pub served: &'static str,
What GitHub says a repository is: its description, homepage and topics. GitHub is the only place these are written; nothing here invents one.
Only ever an http or https address (Meta::parse).
topics may be absent or null; either is none.
109impl Meta {
From the body of GET /repos/{owner}/{name}; None if it is not
that. A description of only blanks is none, and so is a homepage that
is not an http(s) address, so it is never made a link.
113 pub fn parse(body: &str) -> Option<Meta> { 114 let mut meta: Meta = serde_json::from_str(body).ok()?; 115 meta.description = meta.description.map(|text| text.trim().to_owned()).filter(|text| !text.is_empty()); 116 meta.homepage = meta.homepage.map(|url| url.trim().to_owned()).filter(|url| Meta::is_web(url)); 117 meta.topics.retain(|topic| !topic.trim().is_empty()); 118 Some(meta) 119 }
The description, or nothing.
Topic chips, the homepage's link, as the repository's card shows them.
146impl Project {
/lmjtfy.git, which every link on its pages starts with.
Whether it is jevcrates, which other projects depend on rather than clone, and whose pages say how.
jevcrates' crates, and when to depend on which: the "Use it" panel.
160const CRATES: [(&str, &str); 4] = [ 161 ("jev-protocol", "the questions, the request bytes, the checks on an answer; no I/O, builds for wasm"), 162 ("jev-client", "the retry policy, behind your own transport"), 163 ("jev-http", "a native program: HTTP/2 on tokio, with a spend ledger"), 164 ("jev-worker", "a Cloudflare Worker, on its fetch"), 165];
What every code page has around its main pane: which repository, where in it, the whole tree for the explorer, the latest commit if GitHub could be read, and the other repositories.
Which address the page is served at: the code host's pages do not
take part in what the site does live (Host::is_live).
The served path, "" for the repository's root.
How to clone it.
181 pub clone: String,
Every repository the site serves, this one among them.
183 pub projects: Vec<(Project, Option<Meta>)>,
/<repo>.git and /<repo>.git/<path>: the editor. A sidebar (the
explorer, the open document's outline, how to use or clone it, the latest
commit, the other repositories) and a main pane with breadcrumbs, tabs and
the document or file.
198pub fn page(frame: &Frame<'_>, shown: Shown<'_>) -> Markup { 199 let root = frame.path.is_empty(); 200 let served = frame.project.served; 201 let title = match root { 202 true if served == "lmjtfy.git" => "LMJTFY · the code".to_owned(), 203 true => format!("{served} · the code"), 204 false => format!("{served}/{}", frame.path), 205 }; 206 let about = frame.meta.as_ref().map(Meta::about).unwrap_or_default(); 207 let description = match &shown { 208 _ if root => joined(&frame.clone, about), 209 Shown::Dir { .. } => joined(about, &frame.clone), 210 Shown::File { size, body: tree::Body::Text(text), .. } => format!("{} lines · {}", text.lines().count(), bytes(*size)), 211 Shown::File { size, .. } => bytes(*size), 212 Shown::Missing => "Nothing is there.".to_owned(), 213 Shown::Unreachable => "GitHub could not be read.".to_owned(), 214 }; 215 let sha = frame.latest.map(card::Commit::short).unwrap_or(frame.project.branch); 216 // The open document: a folder's README or CLAUDE.md, or a markdown file 217 // rendered. Its headings are the outline; a diagram in it needs mermaid. 218 let file_doc = match &shown { 219 Shown::File { body: tree::Body::Text(text), view, .. } if markdown(frame.path) && *view != View::Source => { 220 Some(Doc::new(frame.path, text, &frame.base())) 221 } 222 _ => None, 223 }; 224 let doc: Option<&Doc> = match &shown { 225 Shown::Dir { docs } => docs.shown(), 226 _ => file_doc.as_ref(), 227 }; 228 let mermaid = doc.is_some_and(|doc| doc.rendered.mermaid); 229 let main = html! { 230 div .crumbs-bar { (crumbs(&frame.base(), served, frame.path)) } 231 @match &shown { 232 Shown::Dir { docs } => (folder(frame, docs)), 233 Shown::File { size, body, view } => (file(frame, *size, body, *view, file_doc.as_ref())), 234 Shown::Missing => (notice("Nothing is here. It may have moved: the explorer has what there is.")), 235 Shown::Unreachable => (notice("GitHub could not be read just now. Try again in a minute, or clone it.")), 236 } 237 }; 238 html! { 239 (DOCTYPE) 240 html lang="en" { 241 (head(preview(frame.origin, served, &title, &description, sha))) 242 body .editor { 243 div .editor-top { (nav(frame.host)) } 244 div .ide { 245 aside .side aria-label="Explorer" { (sidebar(frame, doc)) } 246 main .pane { (main) } 247 } 248 script { (PreEscaped(JS)) } 249 @if mermaid { script type="module" { (PreEscaped(MERMAID)) } } 250 } 251 } 252 } 253}
The sidebar's panels, each one that folds like an editor's.
256fn sidebar(frame: &Frame<'_>, doc: Option<&Doc>) -> Markup { 257 let outline: Vec<&tree::Heading> = 258 doc.map(|doc| doc.rendered.headings.iter().filter(|heading| (2..=3).contains(&heading.level)).collect()).unwrap_or_default(); 259 html! { 260 details .panel open { 261 summary { "Explorer" } 262 div .explorer { 263 a .row .on[frame.path.is_empty()] href=(frame.base()) style="--depth: 0" { (pixel(&tree::icons::open(tree::icons::REPOSITORY))) (frame.project.served) } 264 @if frame.tree.entries.is_empty() { 265 p .verdict { "The tree could not be read from GitHub just now." } 266 } @else { 267 (branch(frame, "", 1)) 268 } 269 } 270 } 271 @if !outline.is_empty() { 272 details .panel open { 273 summary { "Outline" } 274 ol .outline { 275 @for heading in outline { 276 li .sub[heading.level == 3] { a href={ "#" (heading.id) } { (heading.text) } } 277 } 278 } 279 } 280 } 281 @if frame.project.is_jevcrates() { 282 (uses(frame)) 283 } 284 details .panel open { 285 summary { "Clone" } 286 div .panel-body { 287 (clone_line(&frame.clone, html! {})) 288 p .verdict { "No GitHub account needed. It can be cloned and pulled, and nothing else." } 289 } 290 } 291 @if let Some(latest) = frame.latest { 292 details .panel open { 293 summary { "Latest on " (frame.project.branch) } 294 div .panel-body.latest { 295 p { code { (latest.short()) } " " (latest.subject) } 296 p .verdict { (latest.line()) } 297 } 298 } 299 } 300 details .panel open { 301 summary { "Repositories" } 302 ul .projects { 303 @for (project, meta) in &frame.projects { 304 li .on[*project == frame.project] { 305 // The one you are in is the open one. 306 @let icon = if *project == frame.project { tree::icons::open(tree::icons::REPOSITORY) } else { tree::icons::REPOSITORY.to_owned() }; 307 a href=(project.base()) { (pixel(&icon)) (project.served) } 308 @if let Some(meta) = meta.as_ref().filter(|meta| !meta.about().is_empty()) { 309 span .verdict { (meta.about()) } 310 } 311 } 312 } 313 } 314 } 315 } 316}
jevcrates as a dependency: the lines for a project's Cargo.toml, pinned
to the latest commit, through this site, so no GitHub account is needed.
320fn uses(frame: &Frame<'_>) -> Markup { 321 let git = format!("{}{}", frame.origin, frame.base()); 322 let rev = frame.latest.map(|latest| latest.sha.as_str()); 323 html! { 324 details .panel open { 325 summary { "Use it" } 326 div .panel-body { 327 p { "In your project's " code { "Cargo.toml" } ", the crates you need:" } 328 pre .cargo #cargo { 329 "[dependencies]\n" 330 @for (name, _) in CRATES { 331 (name) " = { git = \"" (git) "\"" 332 @if let Some(rev) = rev { ", rev = \"" (rev) "\"" } 333 " }\n" 334 } 335 } 336 button .ghost type="button" data-copy="cargo" { "Copy" } 337 ul .crates { 338 @for (name, what) in CRATES { 339 li { a href={ (frame.base()) "/" (name) "/" } { code { (name) } } " " (what) } 340 } 341 } 342 p .verdict { "Keep the ones you use. Cargo fetches them from here; the rev pins the commit." } 343 } 344 } 345 } 346}
One folder's entries in the explorer, nested. A folder on the way to the open path is open; the open path is marked.
350fn branch(frame: &Frame<'_>, folder: &str, depth: usize) -> Markup { 351 let open = |path: &str| frame.path == path || frame.path.starts_with(&format!("{path}/")); 352 html! { 353 @for entry in frame.tree.children(folder) { 354 @match entry.kind { 355 tree::Kind::Dir | tree::Kind::Submodule => { 356 details .folder open[open(&entry.path)] { 357 summary style={ "--depth: " (depth) } { 358 a .row .on[frame.path == entry.path] href={ (frame.base()) "/" (entry.path) "/" } { 359 (folder_icon(&entry.name)) (entry.name) 360 } 361 } 362 (branch(frame, &entry.path, depth + 1)) 363 } 364 } 365 tree::Kind::File | tree::Kind::Symlink => { 366 a .row .file .on[frame.path == entry.path] href={ (frame.base()) "/" (entry.path) } style={ "--depth: " (depth) } { 367 (pixel(tree::icons::file(&entry.name))) (entry.name) 368 } 369 } 370 } 371 } 372 } 373}
The chapters before and after this folder's, as (label, address): what
its README's guide line says, and for a README with no guide line, the
folder with a README before or after this one in a depth-first walk of
the tree. A guide line that names no Next is the end of its guide.
379fn steps(frame: &Frame<'_>, docs: &Docs) -> (Option<(String, String)>, Option<(String, String)>) { 380 let said = docs.people.as_ref().map(|doc| doc.guide.clone()).unwrap_or_default(); 381 let walk = chapters(frame); 382 let at = walk.iter().position(|path| path == frame.path); 383 let near = |offset: isize| -> Option<(String, String)> { 384 let path = walk.get(at?.checked_add_signed(offset)?)?; 385 let label = if path.is_empty() { frame.project.served.to_owned() } else { format!("{}/", name(path)) }; 386 let href = if path.is_empty() { frame.base() } else { format!("{}/{path}/", frame.base()) }; 387 Some((label, href)) 388 }; 389 if said.line { (said.previous, said.next) } else { (near(-1), near(1)) } 390}
Every folder with a README, the root first, in a depth-first walk in the explorer's order.
394fn chapters(frame: &Frame<'_>) -> Vec<String> { 395 fn walk(tree: &tree::Tree, folder: &str, out: &mut Vec<String>) { 396 let children = tree.children(folder); 397 if children.iter().any(|entry| entry.kind == tree::Kind::File && entry.name.eq_ignore_ascii_case("README.md")) { 398 out.push(folder.to_owned()); 399 } 400 for child in children.iter().filter(|entry| matches!(entry.kind, tree::Kind::Dir | tree::Kind::Submodule)) { 401 walk(tree, &child.path, out); 402 } 403 } 404 let mut out = Vec::new(); 405 walk(frame.tree, "", &mut out); 406 out 407}
A folder in the main pane: its README and CLAUDE.md as tabs, or, with neither, what is in it. The chapter before is a tab on the left, the one after on the right.
412fn folder(frame: &Frame<'_>, docs: &Docs) -> Markup { 413 let base = frame.base(); 414 let here = if frame.path.is_empty() { base.clone() } else { format!("{base}/{}/", frame.path) }; 415 let shown = docs.shown(); 416 let is = |doc: &Option<Doc>| doc.as_ref().map(|doc| doc.path.as_str()) == shown.map(|doc| doc.path.as_str()); 417 let (previous, next) = steps(frame, docs); 418 html! { 419 div .tabs-bar role="tablist" { 420 @if let Some((label, href)) = &previous { 421 a .tab .step .previous href=(href) rel="prev" title={ "Previous: " (label) } { span .arrow aria-hidden="true" { "◀" } span .step-label { (label) } } 422 } 423 @if let Some(people) = &docs.people { 424 a .tab .on[is(&docs.people)] href=(here) role="tab" { (pixel(tree::icons::file(name(&people.path)))) (name(&people.path)) span .tab-note { "for people" } } 425 } 426 @if let Some(agents) = &docs.agents { 427 a .tab .on[is(&docs.agents)] href={ (here) "?view=agents" } role="tab" { (pixel(tree::icons::file(name(&agents.path)))) (name(&agents.path)) span .tab-note { "for agents" } } 428 } 429 @if let Some((label, href)) = &next { 430 a .tab .step .next href=(href) rel="next" title={ "Next: " (label) } { span .step-label { (label) } span .arrow aria-hidden="true" { "▶" } } 431 } 432 } 433 @match shown { 434 Some(doc) => (document(frame, doc)), 435 // Nothing to show and no tree to list: GitHub was not read. 436 None if frame.tree.entries.is_empty() => { 437 (notice("GitHub could not be read just now, so there is nothing to show. Try again in a minute, or clone it.")) 438 } 439 None => { 440 div .doc { ul .contents-list { 441 @for entry in frame.tree.children(frame.path) { 442 li { a href={ (base) "/" (entry.path) @if entry.kind != tree::Kind::File { "/" } } { (entry.name) } } 443 } 444 } } 445 } 446 } 447 } 448}
A document: what it imports, and itself.
451fn document(frame: &Frame<'_>, doc: &Doc) -> Markup { 452 html! { 453 article .doc.md { 454 @if let Some(imports) = &doc.imports { 455 p .imports { 456 "For agents, on top of " a href={ (frame.base()) "/" (imports) } { (name(imports)) } 457 ", which they read first." 458 } 459 } 460 (PreEscaped(&doc.rendered.html)) 461 } 462 } 463}
A file in the main pane: a markdown file rendered or as source, anything else with numbered lines.
467fn file(frame: &Frame<'_>, size: u64, body: &tree::Body, view: View, rendered: Option<&Doc>) -> Markup { 468 let here = format!("{}/{}", frame.base(), frame.path); 469 html! { 470 div .tabs-bar role="tablist" { 471 @if markdown(frame.path) { 472 a .tab .on[view != View::Source] href=(here) role="tab" { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) span .tab-note { "preview" } } 473 a .tab .on[view == View::Source] href={ (here) "?view=source" } role="tab" { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) span .tab-note { "source" } } 474 } @else if tree::annotate::language(frame.path).is_some() { 475 a .tab .on[view != View::Source] href=(here) role="tab" { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) span .tab-note { "annotated" } } 476 a .tab .on[view == View::Source] href={ (here) "?view=source" } role="tab" { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) span .tab-note { "source" } } 477 } @else { 478 span .tab .on { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) } 479 } 480 span .tab-meta { 481 @if let tree::Body::Text(text) = body { (text.lines().count()) " lines · " } 482 (bytes(size)) " · " a href={ (here) "?raw" } { "raw" } 483 } 484 } 485 @match (body, rendered) { 486 (_, Some(doc)) => (document(frame, doc)), 487 (tree::Body::Text(text), None) => (source(frame, text, view)), 488 // A picture is shown; anything else that is not text is offered 489 // as it is. 490 (tree::Body::Binary(_), None) if tree::media_type(frame.path, body).starts_with("image/") => { 491 div .doc.md { img src={ (here) "?raw" } alt=(name(frame.path)); } 492 } 493 (tree::Body::Binary(_), None) => { 494 div .doc { p .verdict { "Not text. " a href={ (here) "?raw" } { "Open it as it is." } } } 495 } 496 (tree::Body::TooLarge, None) => { 497 div .doc { p .verdict { "Too large to show here. Clone it to read it." } } 498 } 499 } 500 } 501}
One of the pixel icons (/icons/<name>.png, third-party/jerrys-pixel-icons).
A folder's two icons, shut and open; the style sheet shows the one that
matches its details.
A source file. In a language known here it is read as Docco reads one
(the owner, 2026-10-03: "extract the docstring put them on the left, and
put the code they map to on the right, with syntax highlighting"): each
run of comments rendered beside the code under it. ?view=source is the
file top to bottom, coloured; a file with no comments of its own lines,
or in no known language, is only that.
524fn source(frame: &Frame<'_>, text: &str, view: View) -> Markup { 525 let Some(language) = tree::annotate::language(frame.path) else { 526 return html! { div .doc.source { pre { 527 @for (index, line) in text.lines().enumerate() { (numbered(index + 1, html! { (line) })) } 528 } } }; 529 }; 530 let sections = tree::annotate::sections(text, language); 531 if view == View::Source || sections.iter().all(|section| section.prose.is_empty()) { 532 return html! { div .doc.source { pre { 533 @for line in tree::annotate::lines(text, language) { (coloured(&line)) } 534 } } }; 535 } 536 let dir = tree::parent(frame.path); 537 html! { 538 div .doc.docco { 539 @for section in §ions { 540 div .sec { 541 div .prose.md { (PreEscaped(tree::markdown(§ion.prose, dir, &frame.base()).html)) } 542 div .source { pre { @for line in §ion.code { (coloured(line)) } } } 543 } 544 } 545 } 546 } 547}
A line of a file with its number, which is also its address (#L12).
559fn coloured(line: &tree::annotate::Line<'_>) -> Markup { 560 numbered( 561 line.number, 562 html! { 563 @for (kind, piece) in &line.pieces { 564 @match kind.class() { 565 Some(class) => span class=(class) { (piece) }, 566 None => (piece), 567 } 568 } 569 }, 570 ) 571} 572 573fn notice(why: &str) -> Markup { 574 html! { div .doc { p .verdict { (why) } } } 575} 576 577fn name(path: &str) -> &str { 578 path.rsplit('/').next().unwrap_or(path) 579}
The diagram drawer, pinned, from jsdelivr, on pages that have a diagram. Themed to the page: ink, paper and pink, square, in the mono face.
583const MERMAID: &str = r##" 584import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@12.1.0/dist/mermaid.esm.min.mjs"; 585mermaid.initialize({ 586 startOnLoad: false, 587 theme: "base", 588 fontFamily: "JetBrains Mono, ui-monospace, monospace", 589 themeVariables: { 590 primaryColor: "#fefefe", primaryTextColor: "#1e1e1e", primaryBorderColor: "#1e1e1e", 591 lineColor: "#1e1e1e", secondaryColor: "#f386a1", tertiaryColor: "#dedede", 592 background: "#dedede", mainBkg: "#fefefe", nodeBorder: "#1e1e1e", clusterBkg: "#dedede", 593 clusterBorder: "#1e1e1e", edgeLabelBackground: "#dedede", fontSize: "14px", 594 }, 595 flowchart: { curve: "linear" }, 596 // The page's look, inside each drawing: boxes are the window title bars 597 // (ink, paper text, square), labels in the pixel face, alternatives and 598 // notes in the pink, everything else in ink on paper. It is inside the 599 // SVG, so it outranks mermaid's own rules and comes along when a drawing 600 // is enlarged. 601 themeCSS: ` 602 rect, polygon { rx: 0; ry: 0; } 603 .actor, .node rect, .node polygon, .node circle, .entityBox, .er.entityBox { 604 fill: #1e1e1e !important; stroke: #1e1e1e !important; } 605 text.actor, text.actor > tspan, .nodeLabel, .node .label, .entityLabel, .er.entityLabel { 606 fill: #fefefe !important; color: #fefefe !important; 607 font-family: VT323, "JetBrains Mono", monospace !important; font-size: 18px !important; } 608 .actor-line { stroke: #1e1e1e !important; stroke-dasharray: 2 3; } 609 .messageLine0, .messageLine1, .flowchart-link, .relationshipLine { stroke: #1e1e1e !important; stroke-width: 1.5px !important; } 610 .messageText, .edgeLabel, .labelText, .loopText, .loopText > tspan { 611 fill: #1e1e1e !important; color: #1e1e1e !important; font-family: "JetBrains Mono", monospace !important; } 612 .edgeLabel, .edgeLabel rect, .labelBkg { background: #fefefe !important; fill: #fefefe !important; } 613 .labelBox, .note { fill: #f386a1 !important; stroke: #1e1e1e !important; } 614 .loopLine { stroke: #1e1e1e !important; stroke-dasharray: 3 3; } 615 #arrowhead path, .arrowheadPath, marker path { fill: #1e1e1e !important; stroke: #1e1e1e !important; } 616 .attributeBoxOdd { fill: #fefefe !important; } .attributeBoxEven { fill: #dedede !important; } 617 .attributeBoxOdd, .attributeBoxEven { stroke: #1e1e1e !important; } 618 .er.attributeBoxOdd + text, text.er { font-family: "JetBrains Mono", monospace !important; } 619 .cluster rect { fill: #dedede !important; stroke: #1e1e1e !important; stroke-dasharray: 1 3; } 620 /* Entities (mermaid 12 draws them as paths with HTML labels): rows of 621 ink text on paper and grey, the name in the pixel face. */ 622 .row-rect-odd path, .row-rect-odd { fill: #fefefe !important; } 623 .row-rect-even path, .row-rect-even { fill: #dedede !important; } 624 .outer-path path, .divider path, .divider { stroke: #1e1e1e !important; } 625 .label.name .nodeLabel, .label.name .nodeLabel p { color: #1e1e1e !important; fill: #1e1e1e !important; 626 font-family: VT323, monospace !important; font-size: 20px !important; } 627 .label.attribute-name .nodeLabel, .label.attribute-type .nodeLabel, .label.attribute-keys .nodeLabel, 628 .label.attribute-comment .nodeLabel, .label.attribute-name .nodeLabel p, .label.attribute-type .nodeLabel p, 629 .label.attribute-keys .nodeLabel p, .label.attribute-comment .nodeLabel p { 630 color: #1e1e1e !important; fill: #1e1e1e !important; font-family: "JetBrains Mono", monospace !important; font-size: 13px !important; } 631 .label.attribute-comment .nodeLabel { opacity: 0.75; } 632 `, 633}); 634await mermaid.run({ querySelector: "pre.mermaid" }); 635"##;
637pub(super) fn preview(origin: &str, served: &str, title: &str, description: &str, sha: &str) -> Markup { 638 // The commit in the picture's address, so a chat fetches it again when 639 // there is a new one. 640 let picture = format!("{origin}/card.png?code={sha}&repo={served}"); 641 html! { 642 title { (title) } 643 meta name="description" content=(description); 644 meta property="og:type" content="website"; 645 meta property="og:site_name" content="LMJTFY · Let Me Jev That For You"; 646 meta property="og:title" content=(title); 647 meta property="og:description" content=(description); 648 meta property="og:image" content=(picture); 649 meta property="og:image:type" content="image/png"; 650 meta property="og:image:width" content=(card::WIDTH); 651 meta property="og:image:height" content=(card::HEIGHT); 652 meta name="twitter:card" content="summary_large_image"; 653 meta name="theme-color" content="#f386a1"; 654 } 655}
lmjtfy.git / packages / rules, each part a link to its folder.
658fn crumbs(base: &str, repo: &str, path: &str) -> Markup { 659 let parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); 660 html! { 661 span .crumbs { 662 a href=(base) { (repo) } 663 @for (index, part) in parts.iter().enumerate() { 664 " / " 665 @if index + 1 == parts.len() { 666 span { (part) } 667 } @else { 668 a href={ (base) "/" (parts[..=index].join("/")) "/" } { (part) } 669 } 670 } 671 } 672 } 673}
335 B, 16.5 KB.
Two parts of a description, with · between if both are there.
The repository's folders are the guide these pages show (the owner, 2026-10-02): every folder a chapter, and every link in a chapter going somewhere. These tests read the working tree, so a folder added without its pages, or a link to a file that moved, fails the build.
693#[cfg(test)] 694mod tests { 695 use super::*; 696 697 const LMJTFY: Project = Project { served: "lmjtfy.git", branch: "main" }; 698 const JEVCRATES: Project = Project { served: "jevcrates.git", branch: "main" }; 699 700 #[test] 701 fn meta_reads_a_full_answer() { 702 let body = r#"{"id":1,"description":"A talking cat.","homepage":"https://whiskers.lmjtfy.fun","topics":["rust","kids"],"private":true}"#; 703 let meta = Meta::parse(body).unwrap(); 704 assert_eq!(meta.about(), "A talking cat."); 705 assert_eq!(meta.homepage.as_deref(), Some("https://whiskers.lmjtfy.fun")); 706 assert_eq!(meta.topics, ["rust", "kids"]); 707 let html = meta.details().into_string(); 708 assert!(html.contains(r#"<span class="topic">rust</span>"#), "{html}"); 709 assert!(html.contains(r#"href="https://whiskers.lmjtfy.fun""#), "{html}"); 710 } 711 712 #[test] 713 fn meta_with_an_empty_or_missing_description_has_none() { 714 assert_eq!(Meta::parse(r#"{"description":" ","topics":[]}"#).unwrap().description, None); 715 assert_eq!(Meta::parse(r#"{"description":null}"#).unwrap().description, None); 716 assert_eq!(Meta::parse(r#"{"description":""}"#).unwrap().about(), ""); 717 } 718 719 #[test] 720 fn meta_with_a_null_or_empty_homepage_has_none() { 721 assert_eq!(Meta::parse(r#"{"homepage":null}"#).unwrap().homepage, None); 722 assert_eq!(Meta::parse(r#"{"homepage":""}"#).unwrap().homepage, None); 723 } 724 725 #[test] 726 fn meta_with_no_topics_has_none() { 727 assert!(Meta::parse(r#"{"description":"x"}"#).unwrap().topics.is_empty()); 728 assert!(Meta::parse(r#"{"topics":null}"#).unwrap().topics.is_empty()); 729 assert!(!Meta::parse(r#"{"topics":[]}"#).unwrap().details().into_string().contains("topic")); 730 } 731 732 #[test] 733 fn meta_never_links_a_homepage_that_is_not_http() { 734 for url in ["javascript:alert(1)", "ftp://x.test", "//x.test", "https://", "https:///x", "data:text/html,x", "https://a b"] { 735 let body = format!(r#"{{"homepage":{}}}"#, serde_json::to_string(url).unwrap()); 736 let meta = Meta::parse(&body).unwrap(); 737 assert_eq!(meta.homepage, None, "{url}"); 738 assert!(!meta.details().into_string().contains("href"), "{url}"); 739 } 740 assert_eq!(Meta::parse(r#"{"homepage":"http://x.test/a"}"#).unwrap().homepage.as_deref(), Some("http://x.test/a")); 741 } 742 743 #[test] 744 fn meta_text_is_escaped() { 745 let meta = Meta::parse(r#"{"topics":["<b>"],"description":"<i>"}"#).unwrap(); 746 assert!(meta.details().into_string().contains("<b>")); 747 } 748 749 #[test] 750 fn meta_is_none_for_what_is_not_a_repository() { 751 assert_eq!(Meta::parse("not json"), None); 752 } 753 754 fn shown(project: Project, latest: Option<&card::Commit>) -> String { 755 let tree = tree::Tree::default(); 756 let frame = Frame { 757 origin: "https://x", 758 host: Host::Other, 759 project, 760 path: "", 761 tree: &tree, 762 latest, 763 clone: format!("git clone https://x/{}", project.served), 764 projects: vec![(LMJTFY, None), (JEVCRATES, None)], 765 meta: None, 766 }; 767 page(&frame, Shown::Dir { docs: Docs { people: None, agents: None, view: View::People } }).into_string() 768 } 769 770 #[test] 771 fn jevcrates_says_how_to_depend_on_it_at_the_latest_commit() { 772 let latest = card::Commit { sha: "4651441abc".into(), subject: "s".into(), date: "2026-10-02".into(), count: None }; 773 let html = shown(JEVCRATES, Some(&latest)); 774 // maud escapes the quotes; the copy button copies the text, unescaped. 775 assert!(html.contains("jev-client = { git = "https://x/jevcrates.git", rev = "4651441abc" }"), "{html}"); 776 assert!(html.contains(r#"href="/jevcrates.git/jev-http/""#), "{html}"); 777 assert!(!shown(LMJTFY, Some(&latest)).contains("Use it")); 778 // With no commit read, the lines still work, unpinned. 779 assert!(shown(JEVCRATES, None).contains("jev-protocol = { git = "https://x/jevcrates.git" }")); 780 } 781 782 #[test] 783 fn a_repository_github_would_not_show_says_so() { 784 assert!(shown(LMJTFY, None).contains("GitHub could not be read just now, so there is nothing to show.")); 785 } 786 787 #[test] 788 fn a_source_file_is_its_comments_beside_its_code() { 789 let tree = tree::parse_tree(r#"{"tree":[],"truncated":false}"#).expect("a tree"); 790 let frame = |path| Frame { origin: "https://x", host: Host::Other, project: LMJTFY, path, tree: &tree, latest: None, clone: String::new(), projects: vec![], meta: None }; 791 let text = "//! The *door*.\n\nuse std::fmt;\n\n/// Adds `<b>`.\nfn add() -> Option<u8> { None } // why\n"; 792 let html = source(&frame("src/lib.rs"), text, View::People).into_string(); 793 // Two runs of comments, each rendered as markdown beside its code. 794 assert_eq!(html.matches(r#"<div class="sec">"#).count(), 2, "{html}"); 795 assert!(html.contains("The <em>door</em>."), "{html}"); 796 // The code keeps its line numbers as addresses, and is coloured. 797 assert!(html.contains(r##"<span class="line" id="L6"><a class="number" href="#L6">6</a>"##), "{html}"); 798 assert!(html.contains(r#"<span class="k">fn</span>"#) && html.contains(r#"<span class="t">Option</span>"#), "{html}"); 799 assert!(html.contains(r#"<span class="c">// why</span>"#), "{html}"); 800 // What a comment or the code says cannot become markup. 801 assert!(!html.contains("<b>"), "{html}"); 802 // The same file top to bottom has every line and no prose column. 803 let plain = source(&frame("src/lib.rs"), text, View::Source).into_string(); 804 assert!(!plain.contains("sec") && plain.contains(r#"id="L1""#) && plain.contains(r#"<span class="c">//! The *door*.</span>"#), "{plain}"); 805 // A file in no known language is its lines, as it was. 806 let other = source(&frame("notes.txt"), "a < b\n", View::People).into_string(); 807 assert!(other.contains("a < b") && !other.contains("sec"), "{other}"); 808 } 809 810 #[test] 811 fn a_chapter_steps_by_its_guide_line_or_else_depth_first() { 812 let listed = r#"[{"path":"README.md","type":"blob","sha":"1"},{"path":"a","type":"tree","sha":"2"},{"path":"a/README.md","type":"blob","sha":"3"}, 813 {"path":"a/b","type":"tree","sha":"4"},{"path":"a/b/README.md","type":"blob","sha":"5"},{"path":"c","type":"tree","sha":"6"},{"path":"c/README.md","type":"blob","sha":"7"}]"#; 814 let tree = tree::parse_tree(&format!(r#"{{"tree":{listed},"truncated":false}}"#)).expect("a tree"); 815 let frame = |path| Frame { origin: "https://x", host: Host::Other, project: LMJTFY, path, tree: &tree, latest: None, clone: String::new(), projects: vec![], meta: None }; 816 let docs = |text: &str, dir: &str| Docs { people: Some(Doc::new(&format!("{dir}/README.md"), text, "/lmjtfy.git")), agents: None, view: View::People }; 817 // No guide line: the walk. a/b comes after a, and c after a/b. 818 let (previous, next) = steps(&frame("a/b"), &docs("# b", "a/b")); 819 assert_eq!(previous, Some(("a/".into(), "/lmjtfy.git/a/".into()))); 820 assert_eq!(next, Some(("c/".into(), "/lmjtfy.git/c/".into()))); 821 // A guide line is followed as it is: here, no Previous. 822 let (previous, next) = steps(&frame("a/b"), &docs("# b\n\nNext: [Chapter 9, elsewhere](../../c/) →", "a/b")); 823 assert_eq!(next, Some(("Chapter 9, elsewhere".into(), "/lmjtfy.git/c/".into()))); 824 assert_eq!(previous, None); 825 // The last chapter of a guide has no Next, and none is made up. 826 assert_eq!(steps(&frame("a/b"), &docs("# b\n\n← Previous: [a](../) · The end.", "a/b")).1, None); 827 let html = folder(&frame("a/b"), &docs("# b", "a/b")).into_string(); 828 assert!(html.contains(r#"rel="prev""#) && html.contains(r#"rel="next""#), "{html}"); 829 // The root has nothing before it. 830 assert_eq!(steps(&frame(""), &docs("# root", "")).0, None); 831 } 832 833 #[test] 834 fn every_page_lists_the_repositories_and_links_under_its_own() { 835 let html = shown(JEVCRATES, None); 836 assert!(html.contains(r#"<li class="on"><a href="/jevcrates.git">"#), "{html}"); 837 assert!(html.contains(r#"<a href="/lmjtfy.git">"#), "{html}"); 838 assert!(html.contains("card.png?code=main&repo=jevcrates.git"), "{html}"); 839 assert!(html.contains("<title>jevcrates.git · the code</title>"), "{html}"); 840 } 841}
Not chapters: tools' state, build output, and jevcrates, which is another repository with its own guide.
852 fn root() -> PathBuf { 853 Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().expect("the repository root") 854 } 855 856 fn folders() -> Vec<PathBuf> { 857 let root = root(); 858 let mut found = vec![root.clone()]; 859 let mut index = 0; 860 while index < found.len() { 861 let entries = std::fs::read_dir(&found[index]).expect("a readable folder"); 862 for entry in entries.flatten() { 863 let path = entry.path(); 864 let relative = path.strip_prefix(&root).unwrap_or(&path).to_string_lossy().into_owned(); 865 if path.is_dir() && !NOT_CHAPTERS.iter().any(|skip| relative == *skip || relative.ends_with(&format!("/{skip}"))) { 866 found.push(path); 867 } 868 } 869 index += 1; 870 } 871 found 872 } 873 874 #[test] 875 fn every_folder_is_a_chapter_with_a_page_for_people_and_one_for_agents() { 876 for folder in folders() { 877 let readme = std::fs::read_to_string(folder.join("README.md")); 878 let readme = readme.unwrap_or_else(|_| panic!("{} has no README.md", folder.display())); 879 assert!(readme.starts_with("# "), "{}: a README starts with its title", folder.display()); 880 let claude = std::fs::read_to_string(folder.join("CLAUDE.md")); 881 let claude = claude.unwrap_or_else(|_| panic!("{} has no CLAUDE.md", folder.display())); 882 assert!(claude.starts_with("@README.md"), "{}: a CLAUDE.md imports its README first", folder.display()); 883 assert!(readme.contains("Next:"), "{}: a chapter ends by leading on", folder.display()); 884 } 885 }
/apps/lmjtfy/ as the guide line's link resolves under base "",
back to the folder it names.
The viewer's arrows follow the chapters' own guide lines, so those lines must be a depth-first walk: every chapter once, each folder before all of its subfolders, and a folder's subtree finished before its next sibling. The chain runs on into jevcrates' guide when the submodule is checked out.
898 #[test] 899 fn the_chapters_lead_on_depth_first_through_every_folder() { 900 let root = root(); 901 let mut chain = vec![String::new()]; 902 loop { 903 let at = chain.last().expect("a chapter"); 904 let Ok(text) = std::fs::read_to_string(root.join(at).join("README.md")) else { break }; 905 let guide = tree::guide(&text, at, ""); 906 if let (Some((_, previous)), Some(before)) = (&guide.previous, chain.len().checked_sub(2).map(|i| &chain[i])) { 907 assert_eq!(&folder_of(previous), before, "{at}: Previous is not the chapter that led here"); 908 } 909 let Some((_, next)) = guide.next else { break }; 910 let next = folder_of(&next); 911 assert!(!chain.contains(&next), "{at}: Next leads back to {next}"); 912 chain.push(next); 913 } 914 let under = |folder: &str, path: &str| folder.is_empty() || path.starts_with(&format!("{folder}/")); 915 for (index, folder) in chain.iter().enumerate() { 916 let after = &chain[index + 1..]; 917 let inside = after.iter().take_while(|path| under(folder, path)).count(); 918 assert!( 919 !after[inside..].iter().any(|path| under(folder, path)), 920 "{folder}: its subfolders are not all read before the guide moves on: {chain:?}" 921 ); 922 } 923 for folder in folders() { 924 let relative = folder.strip_prefix(&root).expect("under the root").to_string_lossy().into_owned(); 925 assert!(chain.contains(&relative), "{relative} is not on the guide's way: {chain:?}"); 926 } 927 }
929 #[test] 930 fn every_relative_link_in_a_chapter_goes_somewhere() { 931 for folder in folders() { 932 let readme = std::fs::read_to_string(folder.join("README.md")).unwrap_or_default(); 933 for target in readme.split("](").skip(1).filter_map(|rest| rest.split(')').next()) { 934 let external = target.contains("://") || target.starts_with('#') || target.starts_with("mailto:"); 935 if external || target.is_empty() { 936 continue; 937 } 938 let path = target.split(['#', '?']).next().unwrap_or(target); 939 assert!(folder.join(path).exists(), "{}: the link `{target}` goes nowhere", folder.join("README.md").display()); 940 } 941 } 942 } 943}