use std::fmt;
macro_rules! verdict_schema {
() => {
"{ \"verdict\": \"pass\" | \"fail\", \"reason\": string, \"anomalies\": string[] }"
};
}
pub trait QuestionPreset {
fn name(&self) -> &'static str;
fn questions(&self) -> Vec<String>;
fn system_prompt(&self) -> Option<&'static str> {
None
}
}
#[must_use]
pub fn extend(base: &str, extension: &str) -> String {
format!("{base}\n\n{extension}")
}
pub const UI_REGRESSION_QUESTION: &str = "\
Checklist: the visible UI is complete and readable. Fail for text clipped or overflowing \
its container, overlapping interactive elements, missing or blank regions where content should \
appear, illegible contrast, or visibly broken layout.";
pub const UI_REGRESSION_SYSTEM_PROMPT: &str = concat!(
"\
You are a UI regression auditor. \
You will be shown one screenshot and asked a specific question. Reply with strict \
JSON matching this schema and nothing else:\n",
verdict_schema!(),
"\nFail criteria: text clipped or overflowing its container, overlapping interactive \
elements, missing/blank regions where content should appear, illegible contrast, \
visibly broken layout. Cosmetic differences from previous runs are NOT failures \
unless they make the UI worse by the criteria above."
);
pub const WEBSITE_INSTALL_QUESTION: &str = "\
Does this page make the install section easy to find, choose from, and act on without layout or \
responsive UX defects?";
pub const WEBSITE_INSTALL_SYSTEM_PROMPT: &str = concat!(
"\
You are auditing a software project website install section. Focus on install flow clarity, \
scanability, heading hierarchy, call-to-action placement, prerequisite visibility, command-copy \
ergonomics, responsive layout, text clipping, overlapping UI, and whether the next step is obvious. \
Reply with strict JSON matching this schema and nothing else:\n",
verdict_schema!()
);
pub const MANUSCRIPT_FIGURE_QUESTION: &str =
"Does this manuscript figure asset pass publication visual QA?";
pub const MANUSCRIPT_FIGURE_SYSTEM_PROMPT: &str = concat!(
"\
You are auditing scientific manuscript figure PNGs at their rendered print size. \
Reply with strict JSON only:\n",
verdict_schema!(),
"\nDo not call tools, inspect files, run commands, or browse. Evaluate only the supplied image. \
Fail if any of these are visible: clipped text or labels, overlapping labels or \
plot marks, illegible axis/legend/caption text, poor contrast for important text, \
blank or placeholder panels, broken legends, missing axes where axes are expected, \
malformed TikZ/rasterization artifacts, cropped arrows/connectors, or composite \
assembly errors such as missing panels, wrong panel order, or inconsistent panel \
lettering. When an asset fails, phrase anomalies so they identify the reusable \
failure class when possible, such as bottom legend clipping, left label margin, \
heatmap label density, annotation collision, TikZ crop margin, or composite \
assembly. Do not fail for minor aesthetic preferences when the asset is readable \
and complete."
);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct UiRegression;
impl QuestionPreset for UiRegression {
fn name(&self) -> &'static str {
"ui-regression"
}
fn questions(&self) -> Vec<String> {
vec![UI_REGRESSION_QUESTION.to_owned()]
}
fn system_prompt(&self) -> Option<&'static str> {
Some(UI_REGRESSION_SYSTEM_PROMPT)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct WebsiteInstall;
impl QuestionPreset for WebsiteInstall {
fn name(&self) -> &'static str {
"website-install"
}
fn questions(&self) -> Vec<String> {
vec![WEBSITE_INSTALL_QUESTION.to_owned()]
}
fn system_prompt(&self) -> Option<&'static str> {
Some(WEBSITE_INSTALL_SYSTEM_PROMPT)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ManuscriptFigure;
impl QuestionPreset for ManuscriptFigure {
fn name(&self) -> &'static str {
"manuscript-figure"
}
fn questions(&self) -> Vec<String> {
vec![MANUSCRIPT_FIGURE_QUESTION.to_owned()]
}
fn system_prompt(&self) -> Option<&'static str> {
Some(MANUSCRIPT_FIGURE_SYSTEM_PROMPT)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PresetError {
NotFound {
name: String,
},
}
impl fmt::Display for PresetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound { name } => {
let available = all().map(QuestionPreset::name).join(", ");
write!(
f,
"unknown question preset {name:?} (available: {available})"
)
}
}
}
}
impl std::error::Error for PresetError {}
#[must_use]
pub fn all() -> [&'static dyn QuestionPreset; 3] {
[&UiRegression, &WebsiteInstall, &ManuscriptFigure]
}
pub fn find(name: &str) -> Result<&'static dyn QuestionPreset, PresetError> {
all()
.into_iter()
.find(|preset| preset.name() == name)
.ok_or_else(|| PresetError::NotFound {
name: name.to_owned(),
})
}
pub fn resolve(name: &str) -> Result<Vec<String>, PresetError> {
find(name).map(QuestionPreset::questions)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preset_names_are_unique() {
let mut names: Vec<_> = all().map(QuestionPreset::name).to_vec();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), all().len());
}
#[test]
fn resolves_each_registered_preset() {
for preset in all() {
let questions = resolve(preset.name()).expect("registered preset resolves");
assert_eq!(questions, preset.questions());
assert!(!questions.is_empty());
}
}
#[test]
fn registered_presets_carry_system_prompts_with_verdict_schema() {
for preset in all() {
let prompt = preset.system_prompt().expect("preset has a system prompt");
assert!(
prompt.contains(verdict_schema!()),
"{} prompt demands the JSON verdict schema",
preset.name()
);
}
}
#[test]
fn preset_texts_stay_project_agnostic() {
for preset in all() {
for text in preset
.questions()
.iter()
.map(String::as_str)
.chain(preset.system_prompt())
{
for project in ["plinth", "Chessbender", "SynDB"] {
assert!(
!text.to_lowercase().contains(&project.to_lowercase()),
"{} preset must not mention {project}",
preset.name()
);
}
}
}
}
#[test]
fn extend_appends_context_paragraph() {
let extended = extend(UI_REGRESSION_SYSTEM_PROMPT, "The screenshots show MyApp.");
assert!(extended.starts_with(UI_REGRESSION_SYSTEM_PROMPT));
assert!(extended.ends_with("\n\nThe screenshots show MyApp."));
}
#[test]
fn unknown_preset_error_lists_available_names() {
let error = resolve("nope").expect_err("unknown preset must fail");
let message = error.to_string();
assert!(message.contains("\"nope\""));
assert!(message.contains("ui-regression"));
assert!(message.contains("website-install"));
assert!(message.contains("manuscript-figure"));
}
}