From bc6dafcf6a1a26d0c997fae4d9aa887de5a7085c Mon Sep 17 00:00:00 2001 From: clover caruso Date: Mon, 5 Oct 2026 00:49:02 -0700 Subject: [PATCH] Support verified Shale r1616 plain issue headings without relaxing identity checks --- dashboard/src/shale.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/dashboard/src/shale.rs b/dashboard/src/shale.rs index 4e739b8e47d2997791e72bb942cd1f1e1a8f74a7..e454af7f42b3dfdda362f36a1db29a2e3d04fb18 100644 --- a/dashboard/src/shale.rs +++ b/dashboard/src/shale.rs @@ -288,16 +288,21 @@ fn issue(html: &str, repository: &str, id: Option) -> Result { let spans: Vec<_> = document .select(&Selector::parse("h1 > span").unwrap()) .collect(); - let issue_id = spans.first().and_then(|s| { - text(*s) - .strip_prefix('#') - .and_then(|s| s.parse::().ok()) - }); + let (issue_id, title) = if spans.len() == 2 { + (text(spans[0]).strip_prefix('#').and_then(|id| id.parse::().ok()), Some(text(spans[1]))) + } else if spans.is_empty() && tokenless_r1616(&document) { + let headings: Vec<_> = document.select(&Selector::parse("h1").unwrap()).collect(); + if let [heading] = headings.as_slice() { + text(*heading).strip_prefix("Issue #").and_then(|text| text.split_once(": ")) + .map(|(id, title)| (id.parse::().ok(), Some(title.to_owned()))) + .unwrap_or((None, None)) + } else { (None, None) } + } else { (None, None) }; let status = document .select(&Selector::parse("select[name=status] option[selected]").unwrap()) .next() .and_then(|e| e.attr("value")); - if spans.len() != 2 + if title.as_ref().is_none_or(|title| title.is_empty()) || issue_id.is_none_or(|n| n == 0 || id.is_some_and(|id| n != id)) || status.is_none() { @@ -334,7 +339,7 @@ fn issue(html: &str, repository: &str, id: Option) -> Result { .map(text) .collect(); Ok( - json!({"repository":repository,"id":issue_id,"title":text(spans[1]),"status":status,"labels":labels,"comments":comments}), + json!({"repository":repository,"id":issue_id,"title":title.unwrap(),"status":status,"labels":labels,"comments":comments}), ) } fn current(app: &App, grant: &Value, credential: &str) -> Result<()> { @@ -921,6 +926,15 @@ mod tests { assert!(issue(&page.replace("selected", ""), "owned", Some(3)).is_err()); } #[test] + fn verified_r1616_issue_heading_keeps_identity_checks() { + let page = "

Issue #3: Snow & ☃

"; + assert_eq!(issue(page, "owned", Some(3)).unwrap()["title"], "Snow & ☃"); + assert!(issue(page, "other", Some(3)).is_err()); + assert!(issue(page, "owned", Some(4)).is_err()); + assert!(issue(&page.replace("r1616-ga87d2f5.zig.0.16.0", "r1758-new"), "owned", Some(3)).is_err()); + assert!(issue(&page.replace("Issue #3", "Issue #0"), "owned", None).is_err()); + } + #[test] fn account_identity_uses_html_text_and_rejects_login_or_changed_markup() { assert_eq!( username("snow&flake☃").unwrap(), -- 2.54.0