Skip to main content

max / makenotwork

Declare the blame header The first converted screen. `declare!` builds the same tree the shape function built, from the same `quasi_router` constructors, at the same moment; what changes is who wrote it. Nothing in the loss list moved: both addresses, both spellings of the line count, and the two ways out of the blame view are asserted by a test that did not exist before, because the shape's own behaviour was covered only through the page around it.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
Author: Max Johnson <me@maxj.phd> · 2026-09-03 17:11 UTC
Signed with PGP, not checked
Commit: cf8005973fe7bc56abcea178e7cab8041f5d9cdb
Parent: 7775c87
3 files changed, +84 insertions, -61 deletions
M server/Cargo.lock +34 -24
@@ -2875,7 +2875,7 @@
2875 2875 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
2876 2876 dependencies = [
2877 2877 "libc",
2878 - "windows-sys 0.52.0",
2878 + "windows-sys 0.59.0",
2879 2879 ]
2880 2880
2881 2881 [[package]]
@@ -4811,7 +4811,7 @@
4811 4811 source = "registry+https://github.com/rust-lang/crates.io-index"
4812 4812 checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
4813 4813 dependencies = [
4814 - "windows-sys 0.61.2",
4814 + "windows-sys 0.59.0",
4815 4815 ]
4816 4816
4817 4817 [[package]]
@@ -5329,6 +5329,7 @@
5329 5329 "proptest",
5330 5330 "quasi-axum",
5331 5331 "quasi-basics",
5332 + "quasi-declare",
5332 5333 "quasi-router",
5333 5334 "quasi-type",
5334 5335 "quasi-webview",
@@ -5722,7 +5723,7 @@
5722 5723 source = "registry+https://github.com/rust-lang/crates.io-index"
5723 5724 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
5724 5725 dependencies = [
5725 - "windows-sys 0.61.2",
5726 + "windows-sys 0.59.0",
5726 5727 ]
5727 5728
5728 5729 [[package]]
@@ -6636,7 +6637,7 @@
6636 6637
6637 6638 [[package]]
6638 6639 name = "quasi-axum"
6639 - version = "0.101.0"
6640 + version = "0.101.1"
6640 6641 dependencies = [
6641 6642 "axum",
6642 6643 "http 1.4.2",
@@ -6647,15 +6648,24 @@
6647 6648
6648 6649 [[package]]
6649 6650 name = "quasi-basics"
6650 - version = "0.101.0"
6651 + version = "0.101.1"
6651 6652 dependencies = [
6652 6653 "makeover-layout",
6653 6654 "quasi-router",
6654 6655 ]
6655 6656
6657 + [[package]]
6658 + name = "quasi-declare"
6659 + version = "0.1.0"
6660 + dependencies = [
6661 + "proc-macro2",
6662 + "quote",
6663 + "syn 2.0.118",
6664 + ]
6665 +
6656 6666 [[package]]
6657 6667 name = "quasi-http"
6658 - version = "0.101.0"
6668 + version = "0.101.1"
6659 6669 dependencies = [
6660 6670 "form_urlencoded",
6661 6671 "http 1.4.2",
@@ -6664,7 +6674,7 @@
6664 6674
6665 6675 [[package]]
6666 6676 name = "quasi-router"
6667 - version = "0.101.0"
6677 + version = "0.101.1"
6668 6678 dependencies = [
6669 6679 "makeover-layout",
6670 6680 ]
@@ -6687,7 +6697,7 @@
6687 6697
6688 6698 [[package]]
6689 6699 name = "quasi-webview"
6690 - version = "0.101.0"
6700 + version = "0.101.1"
6691 6701 dependencies = [
6692 6702 "docengine",
6693 6703 "makeover-layout",
@@ -6763,7 +6773,7 @@
6763 6773 "once_cell",
6764 6774 "socket2",
6765 6775 "tracing",
6766 - "windows-sys 0.52.0",
6776 + "windows-sys 0.59.0",
6767 6777 ]
6768 6778
6769 6779 [[package]]
@@ -7326,7 +7336,7 @@
7326 7336 "errno",
7327 7337 "libc",
7328 7338 "linux-raw-sys 0.4.15",
7329 - "windows-sys 0.52.0",
7339 + "windows-sys 0.59.0",
7330 7340 ]
7331 7341
7332 7342 [[package]]
@@ -7339,7 +7349,7 @@
7339 7349 "errno",
7340 7350 "libc",
7341 7351 "linux-raw-sys 0.12.1",
7342 - "windows-sys 0.52.0",
7352 + "windows-sys 0.59.0",
7343 7353 ]
7344 7354
7345 7355 [[package]]
@@ -7419,7 +7429,7 @@
7419 7429 "security-framework 3.7.0",
7420 7430 "security-framework-sys",
7421 7431 "webpki-root-certs",
7422 - "windows-sys 0.52.0",
7432 + "windows-sys 0.59.0",
7423 7433 ]
7424 7434
7425 7435 [[package]]
@@ -8545,7 +8555,7 @@
8545 8555 "getrandom 0.4.3",
8546 8556 "once_cell",
8547 8557 "rustix 1.1.4",
8548 - "windows-sys 0.52.0",
8558 + "windows-sys 0.59.0",
8549 8559 ]
8550 8560
8551 8561 [[package]]
@@ -10759,13 +10769,21 @@
10759 10769 "pkg-config",
10760 10770 ]
10761 10771
10772 + [[patch.unused]]
10773 + name = "synckit-client"
10774 + version = "0.10.0"
10775 +
10776 + [[patch.unused]]
10777 + name = "synckit-config"
10778 + version = "0.2.0"
10779 +
10762 10780 [[patch.unused]]
10763 10781 name = "quasi-immediate"
10764 - version = "0.101.0"
10782 + version = "0.101.1"
10765 10783
10766 10784 [[patch.unused]]
10767 10785 name = "quasi-notifs"
10768 - version = "0.101.0"
10786 + version = "0.101.1"
10769 10787
10770 10788 [[patch.unused]]
10771 10789 name = "quasi-store"
@@ -10773,7 +10791,7 @@
10773 10791
10774 10792 [[patch.unused]]
10775 10793 name = "quasi-tauri"
10776 - version = "0.101.0"
10794 + version = "0.101.1"
10777 10795
10778 10796 [[patch.unused]]
10779 10797 name = "kberg"
@@ -10786,11 +10804,3 @@
10786 10804 [[patch.unused]]
10787 10805 name = "painhours"
10788 10806 version = "0.1.0"
10789 -
10790 - [[patch.unused]]
10791 - name = "synckit-client"
10792 - version = "0.10.0"
10793 -
10794 - [[patch.unused]]
10795 - name = "synckit-config"
10796 - version = "0.2.0"
@@ -164,6 +164,10 @@
164 164 # here is measured rather than argued. Not load-bearing for any shipped route.
165 165 # See wiki look-wave-2, tier G.
166 166 quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.101" }
167 + # The declared form. A screen written as `declare!` is compiled to the same
168 + # `quasi_router` constructors the hand-written shape called, at build time, so
169 + # nothing new reaches the binary and the macro is not in the request path.
170 + quasi-declare = { git = "https://makenot.work/git/max/quasi.git", version = "0.1" }
167 171 # The description vocabulary quasi's screen types are built from. Pinned here
168 172 # rather than reached through quasi-router's re-export because a described
169 173 # screen names FieldKind and Tone directly; it has to track what quasi-router
@@ -30,8 +30,9 @@
30 30 use std::collections::HashMap;
31 31
32 32 use makeover_layout as layout;
33 + use quasi_declare::declare;
33 34 use quasi_router::screen::{Cell, Cells, Column, Lexeme, Table};
34 - use quasi_router::{Action, Document, Node, RegionKind, Run, Screen, Slot};
35 + use quasi_router::{Action, Document, Node, RegionKind, Screen, Slot};
35 36 use quasi_webview::Webview;
36 37
37 38 use crate::git::{BlameLine, Breadcrumb, RefInfo};
@@ -95,45 +96,27 @@
95 96 .with(page)
96 97 }
97 98
98 - /// The strip over the table: how long the file is, and the other two ways to
99 - /// read it.
100 - fn header(view: &View<'_>) -> Node {
101 - let base = format!("/git/{}/{}", view.owner, view.repo);
99 + declare! {
100 + /// The strip over the table: how long the file is, and the other two ways to
101 + /// read it.
102 + shape header(view: &View<'_>) -> Node;
103 +
104 + let base = "/git/{view.owner}/{view.repo}";
102 105 let count = view.lines.len();
103 - let lines = if count == 1 {
104 - "1 line"
105 - } else {
106 - &format!("{count} lines")
106 + let lines = given count {
107 + 1 -> "1 line",
108 + otherwise -> "{count} lines",
107 109 };
108 110
109 - Node::Region(
110 - Slot::new("git-file-header", RegionKind::Group).across(
111 - Run::new(layout::Fallback::Wrap)
112 - .beside(Node::text(lines), layout::Priority::Secondary)
113 - .beside(
114 - Node::Link {
115 - text: "Source".to_owned(),
116 - action: Action::get(format!(
117 - "{base}/tree/{}/{}",
118 - view.current_ref, view.file_path
119 - ))
120 - .navigating(),
121 - },
122 - layout::Priority::Essential,
123 - )
124 - .beside(
125 - Node::Link {
126 - text: "Raw".to_owned(),
127 - action: Action::get(format!(
128 - "{base}/raw/{}/{}",
129 - view.current_ref, view.file_path
130 - ))
131 - .navigating(),
132 - },
133 - layout::Priority::Essential,
134 - ),
135 - ),
136 - )
111 + region "git-file-header" as Group {
112 + across Wrap {
113 + beside Secondary text lines;
114 + beside Essential link "Source"
115 + to get "{base}/tree/{view.current_ref}/{view.file_path}" navigating;
116 + beside Essential link "Raw"
117 + to get "{base}/raw/{view.current_ref}/{view.file_path}" navigating;
118 + }
119 + }
137 120 }
138 121
139 122 /// One row per line: who last touched it, when, and what it says.
@@ -257,6 +240,32 @@
257 240 Webview::new().screen(&screen(view))
258 241 }
259 242
243 + /// The header is the first declared shape in the tree, so what it must not
244 + /// lose is asserted here rather than left to the shape of the expansion:
245 + /// both addresses, both spellings of the count, and the two ways out of the
246 + /// blame view.
247 + #[test]
248 + fn the_header_says_how_long_the_file_is_and_the_two_ways_to_read_it() {
249 + let annotated = HashMap::new();
250 +
251 + let one = vec![line(1, "abc1234", "fn main() {}")];
252 + let html = rendered(&view(&one, &annotated));
253 + assert!(html.contains("1 line"), "{html}");
254 + assert!(!html.contains("1 lines"), "{html}");
255 +
256 + let two = vec![line(1, "abc1234", "fn main() {}"), line(2, "def5678", "}")];
257 + let html = rendered(&view(&two, &annotated));
258 + assert!(html.contains("2 lines"), "{html}");
259 + assert!(
260 + html.contains(r#"href="/git/ada/engine/tree/main/src/main.rs""#),
261 + "{html}"
262 + );
263 + assert!(
264 + html.contains(r#"href="/git/ada/engine/raw/main/src/main.rs""#),
265 + "{html}"
266 + );
267 + }
268 +
260 269 /// The title names the file being blamed, as the template's did.
261 270 #[test]
262 271 fn the_document_is_titled_for_the_file() {