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};
7use super::{JS, clone_line, head, nav};
What a path came to, to show.
10pub 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.
28impl 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.
44 pub imports: Option<String>,
49impl 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.
54 pub fn new(path: &str, text: &str, base: &str) -> Doc { 55 let dir = tree::parent(path); 56 let name = path.rsplit('/').next().unwrap_or(path); 57 let (imports, text) = 58 if name.eq_ignore_ascii_case("CLAUDE.md") { tree::agents(text) } else { (None, text) }; 59 let imports = imports.map(|imported| if dir.is_empty() { imported.to_owned() } else { format!("{dir}/{imported}") }); 60 Doc { path: path.to_owned(), rendered: tree::markdown(text, dir, base), imports, guide: tree::guide(text, dir, base) } 61 } 62}
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.
90impl 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.
104const CRATES: [(&str, &str); 4] = [ 105 ("jev-protocol", "the questions, the request bytes, the checks on an answer; no I/O, builds for wasm"), 106 ("jev-client", "the retry policy, behind your own transport"), 107 ("jev-http", "a native program: HTTP/2 on tokio, with a spend ledger"), 108 ("jev-worker", "a Cloudflare Worker, on its fetch"), 109];
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.
The served path, "" for the repository's root.
How to clone it.
122 pub clone: String,
/<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.
137pub fn page(frame: &Frame<'_>, shown: Shown<'_>) -> Markup { 138 let root = frame.path.is_empty(); 139 let served = frame.project.served; 140 let title = match root { 141 true if served == "lmjtfy.git" => "LMJTFY · the code".to_owned(), 142 true => format!("{served} · the code"), 143 false => format!("{served}/{}", frame.path), 144 }; 145 let description = match &shown { 146 _ if root => format!("{} · {}", frame.clone, frame.project.blurb), 147 Shown::Dir { .. } => format!("{} · {}", frame.project.blurb, frame.clone), 148 Shown::File { size, body: tree::Body::Text(text), .. } => format!("{} lines · {}", text.lines().count(), bytes(*size)), 149 Shown::File { size, .. } => bytes(*size), 150 Shown::Missing => "Nothing is there.".to_owned(), 151 Shown::Unreachable => "GitHub could not be read.".to_owned(), 152 }; 153 let sha = frame.latest.map(card::Commit::short).unwrap_or(frame.project.branch); 154 // The open document: a folder's README or CLAUDE.md, or a markdown file 155 // rendered. Its headings are the outline; a diagram in it needs mermaid. 156 let file_doc = match &shown { 157 Shown::File { body: tree::Body::Text(text), view, .. } if markdown(frame.path) && *view != View::Source => { 158 Some(Doc::new(frame.path, text, &frame.base())) 159 } 160 _ => None, 161 }; 162 let doc: Option<&Doc> = match &shown { 163 Shown::Dir { docs } => docs.shown(), 164 _ => file_doc.as_ref(), 165 }; 166 let mermaid = doc.is_some_and(|doc| doc.rendered.mermaid); 167 let main = html! { 168 div .crumbs-bar { (crumbs(&frame.base(), served, frame.path)) } 169 @match &shown { 170 Shown::Dir { docs } => (folder(frame, docs)), 171 Shown::File { size, body, view } => (file(frame, *size, body, *view, file_doc.as_ref())), 172 Shown::Missing => (notice("Nothing is here. It may have moved: the explorer has what there is.")), 173 Shown::Unreachable => (notice("GitHub could not be read just now. Try again in a minute, or clone it.")), 174 } 175 }; 176 html! { 177 (DOCTYPE) 178 html lang="en" { 179 (head(preview(frame.origin, served, &title, &description, sha))) 180 body .editor { 181 div .editor-top { (nav()) } 182 div .ide { 183 aside .side aria-label="Explorer" { (sidebar(frame, doc)) } 184 main .pane { (main) } 185 } 186 script { (PreEscaped(JS)) } 187 @if mermaid { script type="module" { (PreEscaped(MERMAID)) } } 188 } 189 } 190 } 191}
The sidebar's panels, each one that folds like an editor's.
194fn sidebar(frame: &Frame<'_>, doc: Option<&Doc>) -> Markup { 195 let outline: Vec<&tree::Heading> = 196 doc.map(|doc| doc.rendered.headings.iter().filter(|heading| (2..=3).contains(&heading.level)).collect()).unwrap_or_default(); 197 html! { 198 details .panel open { 199 summary { "Explorer" } 200 div .explorer { 201 a .row .on[frame.path.is_empty()] href=(frame.base()) style="--depth: 0" { (pixel(&tree::icons::open(tree::icons::REPOSITORY))) (frame.project.served) } 202 @if frame.tree.entries.is_empty() { 203 p .verdict { "The tree could not be read from GitHub just now." } 204 } @else { 205 (branch(frame, "", 1)) 206 } 207 } 208 } 209 @if !outline.is_empty() { 210 details .panel open { 211 summary { "Outline" } 212 ol .outline { 213 @for heading in outline { 214 li .sub[heading.level == 3] { a href={ "#" (heading.id) } { (heading.text) } } 215 } 216 } 217 } 218 } 219 @if frame.project.is_jevcrates() { 220 (uses(frame)) 221 } 222 details .panel open { 223 summary { "Clone" } 224 div .panel-body { 225 (clone_line(&frame.clone, html! {})) 226 p .verdict { "No GitHub account needed. It can be cloned and pulled, and nothing else." } 227 } 228 } 229 @if let Some(latest) = frame.latest { 230 details .panel open { 231 summary { "Latest on " (frame.project.branch) } 232 div .panel-body.latest { 233 p { code { (latest.short()) } " " (latest.subject) } 234 p .verdict { (latest.line()) } 235 } 236 } 237 } 238 details .panel open { 239 summary { "Repositories" } 240 ul .projects { 241 @for project in &frame.projects { 242 li .on[*project == frame.project] { 243 // The one you are in is the open one. 244 @let icon = if *project == frame.project { tree::icons::open(tree::icons::REPOSITORY) } else { tree::icons::REPOSITORY.to_owned() }; 245 a href=(project.base()) { (pixel(&icon)) (project.served) } 246 span .verdict { (project.blurb) } 247 } 248 } 249 } 250 } 251 } 252}
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.
256fn uses(frame: &Frame<'_>) -> Markup { 257 let git = format!("{}{}", frame.origin, frame.base()); 258 let rev = frame.latest.map(|latest| latest.sha.as_str()); 259 html! { 260 details .panel open { 261 summary { "Use it" } 262 div .panel-body { 263 p { "In your project's " code { "Cargo.toml" } ", the crates you need:" } 264 pre .cargo #cargo { 265 "[dependencies]\n" 266 @for (name, _) in CRATES { 267 (name) " = { git = \"" (git) "\"" 268 @if let Some(rev) = rev { ", rev = \"" (rev) "\"" } 269 " }\n" 270 } 271 } 272 button .ghost type="button" data-copy="cargo" { "Copy" } 273 ul .crates { 274 @for (name, what) in CRATES { 275 li { a href={ (frame.base()) "/" (name) "/" } { code { (name) } } " " (what) } 276 } 277 } 278 p .verdict { "Keep the ones you use. Cargo fetches them from here; the rev pins the commit." } 279 } 280 } 281 } 282}
One folder's entries in the explorer, nested. A folder on the way to the open path is open; the open path is marked.
286fn branch(frame: &Frame<'_>, folder: &str, depth: usize) -> Markup { 287 let open = |path: &str| frame.path == path || frame.path.starts_with(&format!("{path}/")); 288 html! { 289 @for entry in frame.tree.children(folder) { 290 @match entry.kind { 291 tree::Kind::Dir | tree::Kind::Submodule => { 292 details .folder open[open(&entry.path)] { 293 summary style={ "--depth: " (depth) } { 294 a .row .on[frame.path == entry.path] href={ (frame.base()) "/" (entry.path) "/" } { 295 (folder_icon(&entry.name)) (entry.name) 296 } 297 } 298 (branch(frame, &entry.path, depth + 1)) 299 } 300 } 301 tree::Kind::File | tree::Kind::Symlink => { 302 a .row .file .on[frame.path == entry.path] href={ (frame.base()) "/" (entry.path) } style={ "--depth: " (depth) } { 303 (pixel(tree::icons::file(&entry.name))) (entry.name) 304 } 305 } 306 } 307 } 308 } 309}
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.
315fn steps(frame: &Frame<'_>, docs: &Docs) -> (Option<(String, String)>, Option<(String, String)>) { 316 let said = docs.people.as_ref().map(|doc| doc.guide.clone()).unwrap_or_default(); 317 let walk = chapters(frame); 318 let at = walk.iter().position(|path| path == frame.path); 319 let near = |offset: isize| -> Option<(String, String)> { 320 let path = walk.get(at?.checked_add_signed(offset)?)?; 321 let label = if path.is_empty() { frame.project.served.to_owned() } else { format!("{}/", name(path)) }; 322 let href = if path.is_empty() { frame.base() } else { format!("{}/{path}/", frame.base()) }; 323 Some((label, href)) 324 }; 325 if said.line { (said.previous, said.next) } else { (near(-1), near(1)) } 326}
Every folder with a README, the root first, in a depth-first walk in the explorer's order.
330fn chapters(frame: &Frame<'_>) -> Vec<String> { 331 fn walk(tree: &tree::Tree, folder: &str, out: &mut Vec<String>) { 332 let children = tree.children(folder); 333 if children.iter().any(|entry| entry.kind == tree::Kind::File && entry.name.eq_ignore_ascii_case("README.md")) { 334 out.push(folder.to_owned()); 335 } 336 for child in children.iter().filter(|entry| matches!(entry.kind, tree::Kind::Dir | tree::Kind::Submodule)) { 337 walk(tree, &child.path, out); 338 } 339 } 340 let mut out = Vec::new(); 341 walk(frame.tree, "", &mut out); 342 out 343}
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.
348fn folder(frame: &Frame<'_>, docs: &Docs) -> Markup { 349 let base = frame.base(); 350 let here = if frame.path.is_empty() { base.clone() } else { format!("{base}/{}/", frame.path) }; 351 let shown = docs.shown(); 352 let is = |doc: &Option<Doc>| doc.as_ref().map(|doc| doc.path.as_str()) == shown.map(|doc| doc.path.as_str()); 353 let (previous, next) = steps(frame, docs); 354 html! { 355 div .tabs-bar role="tablist" { 356 @if let Some((label, href)) = &previous { 357 a .tab .step .previous href=(href) rel="prev" title={ "Previous: " (label) } { span .arrow aria-hidden="true" { "◀" } span .step-label { (label) } } 358 } 359 @if let Some(people) = &docs.people { 360 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" } } 361 } 362 @if let Some(agents) = &docs.agents { 363 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" } } 364 } 365 @if let Some((label, href)) = &next { 366 a .tab .step .next href=(href) rel="next" title={ "Next: " (label) } { span .step-label { (label) } span .arrow aria-hidden="true" { "▶" } } 367 } 368 } 369 @match shown { 370 Some(doc) => (document(frame, doc)), 371 // Nothing to show and no tree to list: GitHub was not read. 372 None if frame.tree.entries.is_empty() => { 373 (notice("GitHub could not be read just now, so there is nothing to show. Try again in a minute, or clone it.")) 374 } 375 None => { 376 div .doc { ul .contents-list { 377 @for entry in frame.tree.children(frame.path) { 378 li { a href={ (base) "/" (entry.path) @if entry.kind != tree::Kind::File { "/" } } { (entry.name) } } 379 } 380 } } 381 } 382 } 383 } 384}
A document: what it imports, and itself.
387fn document(frame: &Frame<'_>, doc: &Doc) -> Markup { 388 html! { 389 article .doc.md { 390 @if let Some(imports) = &doc.imports { 391 p .imports { 392 "For agents, on top of " a href={ (frame.base()) "/" (imports) } { (name(imports)) } 393 ", which they read first." 394 } 395 } 396 (PreEscaped(&doc.rendered.html)) 397 } 398 } 399}
A file in the main pane: a markdown file rendered or as source, anything else with numbered lines.
403fn file(frame: &Frame<'_>, size: u64, body: &tree::Body, view: View, rendered: Option<&Doc>) -> Markup { 404 let here = format!("{}/{}", frame.base(), frame.path); 405 html! { 406 div .tabs-bar role="tablist" { 407 @if markdown(frame.path) { 408 a .tab .on[view != View::Source] href=(here) role="tab" { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) span .tab-note { "preview" } } 409 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" } } 410 } @else if tree::annotate::language(frame.path).is_some() { 411 a .tab .on[view != View::Source] href=(here) role="tab" { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) span .tab-note { "annotated" } } 412 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" } } 413 } @else { 414 span .tab .on { (pixel(tree::icons::file(name(frame.path)))) (name(frame.path)) } 415 } 416 span .tab-meta { 417 @if let tree::Body::Text(text) = body { (text.lines().count()) " lines · " } 418 (bytes(size)) " · " a href={ (here) "?raw" } { "raw" } 419 } 420 } 421 @match (body, rendered) { 422 (_, Some(doc)) => (document(frame, doc)), 423 (tree::Body::Text(text), None) => (source(frame, text, view)), 424 // A picture is shown; anything else that is not text is offered 425 // as it is. 426 (tree::Body::Binary(_), None) if tree::media_type(frame.path, body).starts_with("image/") => { 427 div .doc.md { img src={ (here) "?raw" } alt=(name(frame.path)); } 428 } 429 (tree::Body::Binary(_), None) => { 430 div .doc { p .verdict { "Not text. " a href={ (here) "?raw" } { "Open it as it is." } } } 431 } 432 (tree::Body::TooLarge, None) => { 433 div .doc { p .verdict { "Too large to show here. Clone it to read it." } } 434 } 435 } 436 } 437}
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.
460fn source(frame: &Frame<'_>, text: &str, view: View) -> Markup { 461 let Some(language) = tree::annotate::language(frame.path) else { 462 return html! { div .doc.source { pre { 463 @for (index, line) in text.lines().enumerate() { (numbered(index + 1, html! { (line) })) } 464 } } }; 465 }; 466 let sections = tree::annotate::sections(text, language); 467 if view == View::Source || sections.iter().all(|section| section.prose.is_empty()) { 468 return html! { div .doc.source { pre { 469 @for line in tree::annotate::lines(text, language) { (coloured(&line)) } 470 } } }; 471 } 472 let dir = tree::parent(frame.path); 473 html! { 474 div .doc.docco { 475 @for section in §ions { 476 div .sec { 477 div .prose.md { (PreEscaped(tree::markdown(§ion.prose, dir, &frame.base()).html)) } 478 div .source { pre { @for line in §ion.code { (coloured(line)) } } } 479 } 480 } 481 } 482 } 483}
A line of a file with its number, which is also its address (#L12).
495fn coloured(line: &tree::annotate::Line<'_>) -> Markup { 496 numbered( 497 line.number, 498 html! { 499 @for (kind, piece) in &line.pieces { 500 @match kind.class() { 501 Some(class) => span class=(class) { (piece) }, 502 None => (piece), 503 } 504 } 505 }, 506 ) 507} 508 509fn notice(why: &str) -> Markup { 510 html! { div .doc { p .verdict { (why) } } } 511} 512 513fn name(path: &str) -> &str { 514 path.rsplit('/').next().unwrap_or(path) 515}
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.
519const MERMAID: &str = r##" 520import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@12.1.0/dist/mermaid.esm.min.mjs"; 521mermaid.initialize({ 522 startOnLoad: false, 523 theme: "base", 524 fontFamily: "JetBrains Mono, ui-monospace, monospace", 525 themeVariables: { 526 primaryColor: "#fefefe", primaryTextColor: "#1e1e1e", primaryBorderColor: "#1e1e1e", 527 lineColor: "#1e1e1e", secondaryColor: "#f386a1", tertiaryColor: "#dedede", 528 background: "#dedede", mainBkg: "#fefefe", nodeBorder: "#1e1e1e", clusterBkg: "#dedede", 529 clusterBorder: "#1e1e1e", edgeLabelBackground: "#dedede", fontSize: "14px", 530 }, 531 flowchart: { curve: "linear" }, 532 // The page's look, inside each drawing: boxes are the window title bars 533 // (ink, paper text, square), labels in the pixel face, alternatives and 534 // notes in the pink, everything else in ink on paper. It is inside the 535 // SVG, so it outranks mermaid's own rules and comes along when a drawing 536 // is enlarged. 537 themeCSS: ` 538 rect, polygon { rx: 0; ry: 0; } 539 .actor, .node rect, .node polygon, .node circle, .entityBox, .er.entityBox { 540 fill: #1e1e1e !important; stroke: #1e1e1e !important; } 541 text.actor, text.actor > tspan, .nodeLabel, .node .label, .entityLabel, .er.entityLabel { 542 fill: #fefefe !important; color: #fefefe !important; 543 font-family: VT323, "JetBrains Mono", monospace !important; font-size: 18px !important; } 544 .actor-line { stroke: #1e1e1e !important; stroke-dasharray: 2 3; } 545 .messageLine0, .messageLine1, .flowchart-link, .relationshipLine { stroke: #1e1e1e !important; stroke-width: 1.5px !important; } 546 .messageText, .edgeLabel, .labelText, .loopText, .loopText > tspan { 547 fill: #1e1e1e !important; color: #1e1e1e !important; font-family: "JetBrains Mono", monospace !important; } 548 .edgeLabel, .edgeLabel rect, .labelBkg { background: #fefefe !important; fill: #fefefe !important; } 549 .labelBox, .note { fill: #f386a1 !important; stroke: #1e1e1e !important; } 550 .loopLine { stroke: #1e1e1e !important; stroke-dasharray: 3 3; } 551 #arrowhead path, .arrowheadPath, marker path { fill: #1e1e1e !important; stroke: #1e1e1e !important; } 552 .attributeBoxOdd { fill: #fefefe !important; } .attributeBoxEven { fill: #dedede !important; } 553 .attributeBoxOdd, .attributeBoxEven { stroke: #1e1e1e !important; } 554 .er.attributeBoxOdd + text, text.er { font-family: "JetBrains Mono", monospace !important; } 555 .cluster rect { fill: #dedede !important; stroke: #1e1e1e !important; stroke-dasharray: 1 3; } 556 /* Entities (mermaid 12 draws them as paths with HTML labels): rows of 557 ink text on paper and grey, the name in the pixel face. */ 558 .row-rect-odd path, .row-rect-odd { fill: #fefefe !important; } 559 .row-rect-even path, .row-rect-even { fill: #dedede !important; } 560 .outer-path path, .divider path, .divider { stroke: #1e1e1e !important; } 561 .label.name .nodeLabel, .label.name .nodeLabel p { color: #1e1e1e !important; fill: #1e1e1e !important; 562 font-family: VT323, monospace !important; font-size: 20px !important; } 563 .label.attribute-name .nodeLabel, .label.attribute-type .nodeLabel, .label.attribute-keys .nodeLabel, 564 .label.attribute-comment .nodeLabel, .label.attribute-name .nodeLabel p, .label.attribute-type .nodeLabel p, 565 .label.attribute-keys .nodeLabel p, .label.attribute-comment .nodeLabel p { 566 color: #1e1e1e !important; fill: #1e1e1e !important; font-family: "JetBrains Mono", monospace !important; font-size: 13px !important; } 567 .label.attribute-comment .nodeLabel { opacity: 0.75; } 568 `, 569}); 570await mermaid.run({ querySelector: "pre.mermaid" }); 571"##;
573fn preview(origin: &str, served: &str, title: &str, description: &str, sha: &str) -> Markup { 574 // The commit in the picture's address, so a chat fetches it again when 575 // there is a new one. 576 let picture = format!("{origin}/card.png?code={sha}&repo={served}"); 577 html! { 578 title { (title) } 579 meta name="description" content=(description); 580 meta property="og:type" content="website"; 581 meta property="og:site_name" content="LMJTFY · Let Me Jev That For You"; 582 meta property="og:title" content=(title); 583 meta property="og:description" content=(description); 584 meta property="og:image" content=(picture); 585 meta property="og:image:type" content="image/png"; 586 meta property="og:image:width" content=(card::WIDTH); 587 meta property="og:image:height" content=(card::HEIGHT); 588 meta name="twitter:card" content="summary_large_image"; 589 meta name="theme-color" content="#f386a1"; 590 } 591}
lmjtfy.git / packages / rules, each part a link to its folder.
594fn crumbs(base: &str, repo: &str, path: &str) -> Markup { 595 let parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); 596 html! { 597 span .crumbs { 598 a href=(base) { (repo) } 599 @for (index, part) in parts.iter().enumerate() { 600 " / " 601 @if index + 1 == parts.len() { 602 span { (part) } 603 } @else { 604 a href={ (base) "/" (parts[..=index].join("/")) "/" } { (part) } 605 } 606 } 607 } 608 } 609}
335 B, 16.5 KB.
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.
624#[cfg(test)] 625mod tests { 626 use super::*; 627 628 const LMJTFY: Project = Project { served: "lmjtfy.git", blurb: "This site.", branch: "main" }; 629 const JEVCRATES: Project = Project { served: "jevcrates.git", blurb: "The client.", branch: "main" }; 630 631 fn shown(project: Project, latest: Option<&card::Commit>) -> String { 632 let tree = tree::Tree::default(); 633 let frame = Frame { 634 origin: "https://x", 635 project, 636 path: "", 637 tree: &tree, 638 latest, 639 clone: format!("git clone https://x/{}", project.served), 640 projects: vec![LMJTFY, JEVCRATES], 641 }; 642 page(&frame, Shown::Dir { docs: Docs { people: None, agents: None, view: View::People } }).into_string() 643 } 644 645 #[test] 646 fn jevcrates_says_how_to_depend_on_it_at_the_latest_commit() { 647 let latest = card::Commit { sha: "4651441abc".into(), subject: "s".into(), date: "2026-10-02".into(), count: None }; 648 let html = shown(JEVCRATES, Some(&latest)); 649 // maud escapes the quotes; the copy button copies the text, unescaped. 650 assert!(html.contains("jev-client = { git = "https://x/jevcrates.git", rev = "4651441abc" }"), "{html}"); 651 assert!(html.contains(r#"href="/jevcrates.git/jev-http/""#), "{html}"); 652 assert!(!shown(LMJTFY, Some(&latest)).contains("Use it")); 653 // With no commit read, the lines still work, unpinned. 654 assert!(shown(JEVCRATES, None).contains("jev-protocol = { git = "https://x/jevcrates.git" }")); 655 } 656 657 #[test] 658 fn a_repository_github_would_not_show_says_so() { 659 assert!(shown(LMJTFY, None).contains("GitHub could not be read just now, so there is nothing to show.")); 660 } 661 662 #[test] 663 fn a_source_file_is_its_comments_beside_its_code() { 664 let tree = tree::parse_tree(r#"{"tree":[],"truncated":false}"#).expect("a tree"); 665 let frame = |path| Frame { origin: "https://x", project: LMJTFY, path, tree: &tree, latest: None, clone: String::new(), projects: vec![] }; 666 let text = "//! The *door*.\n\nuse std::fmt;\n\n/// Adds `<b>`.\nfn add() -> Option<u8> { None } // why\n"; 667 let html = source(&frame("src/lib.rs"), text, View::People).into_string(); 668 // Two runs of comments, each rendered as markdown beside its code. 669 assert_eq!(html.matches(r#"<div class="sec">"#).count(), 2, "{html}"); 670 assert!(html.contains("The <em>door</em>."), "{html}"); 671 // The code keeps its line numbers as addresses, and is coloured. 672 assert!(html.contains(r##"<span class="line" id="L6"><a class="number" href="#L6">6</a>"##), "{html}"); 673 assert!(html.contains(r#"<span class="k">fn</span>"#) && html.contains(r#"<span class="t">Option</span>"#), "{html}"); 674 assert!(html.contains(r#"<span class="c">// why</span>"#), "{html}"); 675 // What a comment or the code says cannot become markup. 676 assert!(!html.contains("<b>"), "{html}"); 677 // The same file top to bottom has every line and no prose column. 678 let plain = source(&frame("src/lib.rs"), text, View::Source).into_string(); 679 assert!(!plain.contains("sec") && plain.contains(r#"id="L1""#) && plain.contains(r#"<span class="c">//! The *door*.</span>"#), "{plain}"); 680 // A file in no known language is its lines, as it was. 681 let other = source(&frame("notes.txt"), "a < b\n", View::People).into_string(); 682 assert!(other.contains("a < b") && !other.contains("sec"), "{other}"); 683 } 684 685 #[test] 686 fn a_chapter_steps_by_its_guide_line_or_else_depth_first() { 687 let listed = r#"[{"path":"README.md","type":"blob","sha":"1"},{"path":"a","type":"tree","sha":"2"},{"path":"a/README.md","type":"blob","sha":"3"}, 688 {"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"}]"#; 689 let tree = tree::parse_tree(&format!(r#"{{"tree":{listed},"truncated":false}}"#)).expect("a tree"); 690 let frame = |path| Frame { origin: "https://x", project: LMJTFY, path, tree: &tree, latest: None, clone: String::new(), projects: vec![] }; 691 let docs = |text: &str, dir: &str| Docs { people: Some(Doc::new(&format!("{dir}/README.md"), text, "/lmjtfy.git")), agents: None, view: View::People }; 692 // No guide line: the walk. a/b comes after a, and c after a/b. 693 let (previous, next) = steps(&frame("a/b"), &docs("# b", "a/b")); 694 assert_eq!(previous, Some(("a/".into(), "/lmjtfy.git/a/".into()))); 695 assert_eq!(next, Some(("c/".into(), "/lmjtfy.git/c/".into()))); 696 // A guide line is followed as it is: here, no Previous. 697 let (previous, next) = steps(&frame("a/b"), &docs("# b\n\nNext: [Chapter 9, elsewhere](../../c/) →", "a/b")); 698 assert_eq!(next, Some(("Chapter 9, elsewhere".into(), "/lmjtfy.git/c/".into()))); 699 assert_eq!(previous, None); 700 // The last chapter of a guide has no Next, and none is made up. 701 assert_eq!(steps(&frame("a/b"), &docs("# b\n\n← Previous: [a](../) · The end.", "a/b")).1, None); 702 let html = folder(&frame("a/b"), &docs("# b", "a/b")).into_string(); 703 assert!(html.contains(r#"rel="prev""#) && html.contains(r#"rel="next""#), "{html}"); 704 // The root has nothing before it. 705 assert_eq!(steps(&frame(""), &docs("# root", "")).0, None); 706 } 707 708 #[test] 709 fn every_page_lists_the_repositories_and_links_under_its_own() { 710 let html = shown(JEVCRATES, None); 711 assert!(html.contains(r#"<li class="on"><a href="/jevcrates.git">"#), "{html}"); 712 assert!(html.contains(r#"<a href="/lmjtfy.git">"#), "{html}"); 713 assert!(html.contains("card.png?code=main&repo=jevcrates.git"), "{html}"); 714 assert!(html.contains("<title>jevcrates.git · the code</title>"), "{html}"); 715 } 716}
Not chapters: tools' state, build output, and jevcrates, which is another repository with its own guide.
727 fn root() -> PathBuf { 728 Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().expect("the repository root") 729 } 730 731 fn folders() -> Vec<PathBuf> { 732 let root = root(); 733 let mut found = vec![root.clone()]; 734 let mut index = 0; 735 while index < found.len() { 736 let entries = std::fs::read_dir(&found[index]).expect("a readable folder"); 737 for entry in entries.flatten() { 738 let path = entry.path(); 739 let relative = path.strip_prefix(&root).unwrap_or(&path).to_string_lossy().into_owned(); 740 if path.is_dir() && !NOT_CHAPTERS.iter().any(|skip| relative == *skip || relative.ends_with(&format!("/{skip}"))) { 741 found.push(path); 742 } 743 } 744 index += 1; 745 } 746 found 747 } 748 749 #[test] 750 fn every_folder_is_a_chapter_with_a_page_for_people_and_one_for_agents() { 751 for folder in folders() { 752 let readme = std::fs::read_to_string(folder.join("README.md")); 753 let readme = readme.unwrap_or_else(|_| panic!("{} has no README.md", folder.display())); 754 assert!(readme.starts_with("# "), "{}: a README starts with its title", folder.display()); 755 let claude = std::fs::read_to_string(folder.join("CLAUDE.md")); 756 let claude = claude.unwrap_or_else(|_| panic!("{} has no CLAUDE.md", folder.display())); 757 assert!(claude.starts_with("@README.md"), "{}: a CLAUDE.md imports its README first", folder.display()); 758 assert!(readme.contains("Next:"), "{}: a chapter ends by leading on", folder.display()); 759 } 760 }
/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.
773 #[test] 774 fn the_chapters_lead_on_depth_first_through_every_folder() { 775 let root = root(); 776 let mut chain = vec![String::new()]; 777 loop { 778 let at = chain.last().expect("a chapter"); 779 let Ok(text) = std::fs::read_to_string(root.join(at).join("README.md")) else { break }; 780 let guide = tree::guide(&text, at, ""); 781 if let (Some((_, previous)), Some(before)) = (&guide.previous, chain.len().checked_sub(2).map(|i| &chain[i])) { 782 assert_eq!(&folder_of(previous), before, "{at}: Previous is not the chapter that led here"); 783 } 784 let Some((_, next)) = guide.next else { break }; 785 let next = folder_of(&next); 786 assert!(!chain.contains(&next), "{at}: Next leads back to {next}"); 787 chain.push(next); 788 } 789 let under = |folder: &str, path: &str| folder.is_empty() || path.starts_with(&format!("{folder}/")); 790 for (index, folder) in chain.iter().enumerate() { 791 let after = &chain[index + 1..]; 792 let inside = after.iter().take_while(|path| under(folder, path)).count(); 793 assert!( 794 !after[inside..].iter().any(|path| under(folder, path)), 795 "{folder}: its subfolders are not all read before the guide moves on: {chain:?}" 796 ); 797 } 798 for folder in folders() { 799 let relative = folder.strip_prefix(&root).expect("under the root").to_string_lossy().into_owned(); 800 assert!(chain.contains(&relative), "{relative} is not on the guide's way: {chain:?}"); 801 } 802 }
804 #[test] 805 fn every_relative_link_in_a_chapter_goes_somewhere() { 806 for folder in folders() { 807 let readme = std::fs::read_to_string(folder.join("README.md")).unwrap_or_default(); 808 for target in readme.split("](").skip(1).filter_map(|rest| rest.split(')').next()) { 809 let external = target.contains("://") || target.starts_with('#') || target.starts_with("mailto:"); 810 if external || target.is_empty() { 811 continue; 812 } 813 let path = target.split(['#', '?']).next().unwrap_or(target); 814 assert!(folder.join(path).exists(), "{}: the link `{target}` goes nowhere", folder.join("README.md").display()); 815 } 816 } 817 } 818}