roots: complete issue assignees from a members datalist
commit
4790a9croots: complete issue assignees from a members datalist
Assisted-by: Claude:claude-fable-5
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
crates/cli/ents-web/tests/router.rs
@@ -2310,6 +2310,26 @@
);
}
+/// The issues page carries a `datalist#members` of enrolled usernames so
+/// the assignees field completes by member id in place.
+#[tokio::test]
+async fn issue_forms_carry_a_members_datalist() {
+ let dir = seed_repo(&[("README.md", "# hi\n")]);
+ let state = build_state_at(
+ FixtureIdentity {
+ name: "local-user",
+ key: Keypair::from_seed(1),
+ },
+ dir.path().to_owned(),
+ );
+ let router = ents_web::router(state);
+ let body = get_body(&router, "/issues").await;
+ assert!(
+ body.contains("datalist id=\"members\""),
+ "the assignees field has a members datalist to complete from: {body}"
+ );
+}
+
/// A show page for an id with no ref at all is a real 404, not a 500 --
/// `ents_forge::Error::NotFound` keeps its status through the `Forge`
/// box (the box exists for variant-size hygiene only).
crates/cli/ents-web/src/pages/issues.rs
@@ -81,6 +81,7 @@
}
h2 { "Open an Issue" }
(new_form(&session))
+ (super::members_datalist(&state))
}
},
))
@@ -151,6 +152,7 @@
details {
summary { "Edit" }
(edit_form(&session, &issue))
+ (super::members_datalist(&state))
}
h2 { "Discussion" }
(crate::pages::comments::thread_section(&state, &session, &thread, &return_to))
@@ -351,7 +353,7 @@
input type="text" name="state" value="open" list="issue-states";
}
(state_datalist())
- label { "assignees" input type="text" name="assignees" placeholder="alice, bob"; }
+ label { "assignees" input type="text" name="assignees" placeholder="alice, bob" list="members"; }
label { "labels" input type="text" name="labels" placeholder="bug, gate"; }
label { "body" textarea name="body" {} }
button type="submit" { "Open Issue" }
@@ -373,7 +375,7 @@
(state_datalist())
label {
"assignees"
- input type="text" name="assignees" value=(join_members(&issue.assignees));
+ input type="text" name="assignees" value=(join_members(&issue.assignees)) list="members";
}
label { "labels" input type="text" name="labels" value=(issue.labels.join(", ")); }
button type="submit" { "Save" }
crates/cli/ents-web/src/pages/mod.rs
@@ -407,6 +407,29 @@
}
}
+/// A `<datalist id="members">` of every enrolled username
+/// (`refs/meta/member/*`), for forms whose text field names a member --
+/// an issue's assignees completes by id in place; richer matching (by
+/// key, fuzzy) stays with the palette. Best-effort: a ref-store read
+/// failure renders an empty datalist rather than failing the page the
+/// form sits on.
+pub(crate) fn members_datalist<O>(state: &AppState<O>) -> Markup {
+ let mut names = Vec::new();
+ if let Ok(entries) = state.refs.iter_prefix("refs/meta/member/") {
+ for (name, _tip) in entries.flatten() {
+ let path = name.as_bstr().to_string();
+ if let Some(username) = path.strip_prefix("refs/meta/member/") {
+ names.push(username.to_owned());
+ }
+ }
+ }
+ html! {
+ datalist id="members" {
+ @for name in &names { option value=(name) {} }
+ }
+ }
+}
+
/// The one-level breadcrumb trail every `/{id}` child page renders above
/// its own content -- "parent \u{203a} here", reusing the `.crumbs` markup
/// pattern [`super::files`]'s own multi-level path trail already renders