style: apply rustfmt across the AI-subsystem crates

CI's Rust Check runs a workspace-wide `cargo fmt --check`. Reformat the
new op-acp / op-ai-skills crates and the Part A chat changes to rustfmt
canon. Also sweeps two files an earlier commit left non-compliant
(canvas_viewport_paint.rs, op-pen-loader/adapter.rs) so the workspace
check is clean. Formatting only — no behaviour change.
This commit is contained in:
Kayshen-X 2026-05-17 22:50:27 +08:00
parent 4adfa38bae
commit 5b6874b7d8
21 changed files with 142 additions and 101 deletions

View file

@ -13,8 +13,8 @@ use tokio::task::JoinHandle;
use crate::jsonrpc::{dispatch_inbound, JsonRpcEngine};
use crate::protocol::{
InitializeResult, NewSessionResult, SessionNotification, METHOD_INITIALIZE,
METHOD_SESSION_NEW, METHOD_SESSION_PROMPT, PROTOCOL_VERSION,
InitializeResult, NewSessionResult, SessionNotification, METHOD_INITIALIZE, METHOD_SESSION_NEW,
METHOD_SESSION_PROMPT, PROTOCOL_VERSION,
};
use crate::transport::{read_frame, write_frame};
use crate::types::{AcpAgentConfig, AcpAgentInfo, AcpError, ConnectionType};
@ -63,9 +63,7 @@ impl AcpConnection {
let mut buf = BufReader::new(read);
loop {
match read_frame(&mut buf).await {
Ok(Some(value)) => {
dispatch_inbound(value, &pending, &notif_tx, &reply_tx)
}
Ok(Some(value)) => dispatch_inbound(value, &pending, &notif_tx, &reply_tx),
// EOF or transport failure — stop reading.
Ok(None) | Err(_) => break,
}
@ -291,10 +289,7 @@ mod tests {
/// A mock ACP agent: answers `initialize` / `session/new`, then on
/// `session/prompt` streams one message chunk and returns.
async fn mock_agent(
read: impl AsyncRead + Unpin,
mut write: impl AsyncWrite + Unpin,
) {
async fn mock_agent(read: impl AsyncRead + Unpin, mut write: impl AsyncWrite + Unpin) {
let mut buf = BufReader::new(read);
while let Ok(Some(frame)) = read_frame(&mut buf).await {
let id = frame.get("id").cloned().unwrap_or(Value::Null);
@ -352,7 +347,9 @@ mod tests {
let session = conn.new_session().await.expect("new_session");
assert_eq!(session, "sess-1");
conn.prompt(&session, "design a button").await.expect("prompt");
conn.prompt(&session, "design a button")
.await
.expect("prompt");
// The streamed chunk reached the notification channel.
let note = notes.recv().await.expect("a session/update");
assert_eq!(note.session_id.as_deref(), Some("sess-1"));
@ -372,7 +369,10 @@ mod tests {
// The reader drains pending requests on EOF, so the call
// resolves to `Closed` at once rather than after the 30s
// handshake timeout.
assert!(matches!(err, AcpError::Closed), "expected Closed, got {err:?}");
assert!(
matches!(err, AcpError::Closed),
"expected Closed, got {err:?}"
);
assert!(
started.elapsed() < Duration::from_secs(5),
"must fail fast, not wait out the timeout"

View file

@ -53,8 +53,7 @@ pub fn session_update_to_delta(note: &SessionNotification) -> Option<ChatDelta>
..
} => match status.as_deref() {
Some("failed") => {
let msg = extract_tool_error(content)
.unwrap_or_else(|| raw_output.to_string());
let msg = extract_tool_error(content).unwrap_or_else(|| raw_output.to_string());
Some(ChatDelta::Error(msg))
}
// `completed` is not a stream terminator — the turn ends

View file

@ -66,8 +66,7 @@ impl JsonRpcEngine {
self.pending.lock().unwrap().insert(id, tx);
let req = JsonRpcRequest::new(id, method, params);
let frame =
serde_json::to_value(&req).map_err(|e| AcpError::Protocol(e.to_string()))?;
let frame = serde_json::to_value(&req).map_err(|e| AcpError::Protocol(e.to_string()))?;
if self.out_tx.send(frame).is_err() {
self.pending.lock().unwrap().remove(&id);
return Err(AcpError::Closed);
@ -79,9 +78,7 @@ impl JsonRpcEngine {
Ok(Err(_)) => Err(AcpError::Closed),
Err(_) => {
self.pending.lock().unwrap().remove(&id);
Err(AcpError::Transport(format!(
"request '{method}' timed out"
)))
Err(AcpError::Transport(format!("request '{method}' timed out")))
}
}
}
@ -91,8 +88,8 @@ impl JsonRpcEngine {
/// request and build the JSON-RPC result that selects it. Mirrors the
/// TS `requestPermission` handler.
pub fn auto_approve_permission(params: &Value) -> Value {
let parsed: RequestPermissionParams =
serde_json::from_value(params.clone()).unwrap_or(RequestPermissionParams { options: vec![] });
let parsed: RequestPermissionParams = serde_json::from_value(params.clone())
.unwrap_or(RequestPermissionParams { options: vec![] });
let chosen = parsed
.options
.iter()

View file

@ -249,7 +249,10 @@ mod tests {
classify_inbound(&note),
Inbound::Notification { .. }
));
assert!(matches!(classify_inbound(&serde_json::json!(5)), Inbound::Unknown));
assert!(matches!(
classify_inbound(&serde_json::json!(5)),
Inbound::Unknown
));
}
#[test]

View file

@ -48,8 +48,7 @@ pub async fn write_frame(
writer: &mut (impl AsyncWrite + Unpin),
value: &Value,
) -> Result<(), AcpError> {
let mut bytes =
serde_json::to_vec(value).map_err(|e| AcpError::Protocol(e.to_string()))?;
let mut bytes = serde_json::to_vec(value).map_err(|e| AcpError::Protocol(e.to_string()))?;
bytes.push(b'\n');
writer
.write_all(&bytes)

View file

@ -208,9 +208,7 @@ pub fn parse_skill_frontmatter(raw: &str) -> Option<(SkillMeta, String)> {
.filter_map(|s| Phase::from_str(s))
.collect()
} else {
Phase::from_str(&unquote(&e.value))
.into_iter()
.collect()
Phase::from_str(&unquote(&e.value)).into_iter().collect()
};
}
"priority" => priority = unquote(&e.value).parse().unwrap_or(50),

View file

@ -37,11 +37,7 @@ fn collect(dir: &Dir, out: &mut Vec<SkillEntry>) {
collect(sub, out);
}
for file in dir.files() {
let is_md = file
.path()
.extension()
.map(|e| e == "md")
.unwrap_or(false);
let is_md = file.path().extension().map(|e| e == "md").unwrap_or(false);
if !is_md {
continue;
}
@ -122,8 +118,8 @@ mod tests {
// list. All three must parse to a non-empty keyword trigger
// (a regression here silently disables the skill).
for name in ["role-definitions", "copywriting", "cjk-typography"] {
let skill = get_skill_by_name(name)
.unwrap_or_else(|| panic!("{name} should be registered"));
let skill =
get_skill_by_name(name).unwrap_or_else(|| panic!("{name} should be registered"));
match &skill.meta.trigger {
crate::types::SkillTrigger::Keywords(kw) => {
assert!(!kw.is_empty(), "{name} keyword trigger must not be empty");

View file

@ -132,7 +132,9 @@ mod tests {
aesthetic: Some("minimal".into()),
}),
subtasks: Some(vec![
PlanSubtask { label: "hero".into() },
PlanSubtask {
label: "hero".into(),
},
PlanSubtask {
label: "pricing".into(),
},
@ -141,7 +143,10 @@ mod tests {
let out = extract_design_context(&base, &plan, "t1");
assert_eq!(out.updated_at, "t1");
assert_eq!(out.design_system.palette, vec!["#000", "#fff"]);
assert_eq!(out.design_system.typography.as_deref(), Some("Inter, Playfair"));
assert_eq!(
out.design_system.typography.as_deref(),
Some("Inter, Playfair")
);
assert_eq!(out.design_system.aesthetic.as_deref(), Some("minimal"));
assert_eq!(out.structure.sections, vec!["hero", "pricing"]);
}

View file

@ -45,18 +45,13 @@ fn format_recent_history(history: &[crate::types::HistoryEntry]) -> String {
}
/// Resolve the skill set for `phase` against `user_message`.
pub fn resolve_skills(
phase: Phase,
user_message: &str,
options: &ResolveOptions,
) -> AgentContext {
pub fn resolve_skills(phase: Phase, user_message: &str, options: &ResolveOptions) -> AgentContext {
let total_budget = options
.budget_override
.unwrap_or_else(|| phase.default_budget());
// Steps 1 + 2 — phase filter, then intent / flag match.
let phase_skills: Vec<SkillEntry> =
get_skills_by_phase(phase).into_iter().cloned().collect();
let phase_skills: Vec<SkillEntry> = get_skills_by_phase(phase).into_iter().cloned().collect();
let matched = filter_by_intent(&phase_skills, user_message, &options.flags);
// Per-phase memory loading (done before injection so history is
@ -139,10 +134,17 @@ mod tests {
#[test]
fn generation_resolve_stays_within_budget() {
let ctx = resolve_skills(Phase::Generation, "design a login form", &ResolveOptions::default());
let ctx = resolve_skills(
Phase::Generation,
"design a login form",
&ResolveOptions::default(),
);
assert_eq!(ctx.budget_max, 8000);
assert!(ctx.budget_used <= ctx.budget_max);
assert!(!ctx.skills.is_empty(), "generation phase should resolve skills");
assert!(
!ctx.skills.is_empty(),
"generation phase should resolve skills"
);
}
#[test]
@ -200,7 +202,11 @@ mod tests {
#[test]
fn budget_override_is_honored() {
let high = resolve_skills(Phase::Generation, "design something", &ResolveOptions::default());
let high = resolve_skills(
Phase::Generation,
"design something",
&ResolveOptions::default(),
);
let opts = ResolveOptions {
budget_override: Some(500),
..Default::default()

View file

@ -55,9 +55,7 @@ pub fn match_trigger(
.any(|kw| match_keyword(&msg, &kw.to_lowercase()))
}
// Every named flag must be present and `true`.
SkillTrigger::Flags(needed) => {
needed.iter().all(|f| flags.get(f).copied() == Some(true))
}
SkillTrigger::Flags(needed) => needed.iter().all(|f| flags.get(f).copied() == Some(true)),
}
}
@ -167,11 +165,7 @@ mod tests {
fn filter_keeps_matches_and_sorts_by_priority() {
let skills = vec![
skill("late", SkillTrigger::Always, 90),
skill(
"kw",
SkillTrigger::Keywords(vec!["dashboard".into()]),
10,
),
skill("kw", SkillTrigger::Keywords(vec!["dashboard".into()]), 10),
skill("early", SkillTrigger::Always, 5),
];
let out = filter_by_intent(&skills, "build a dashboard", &HashMap::new());

View file

@ -61,11 +61,7 @@ pub fn style_guide_registry() -> &'static [ParsedStyleGuide] {
let mut out = Vec::new();
if let Some(dir) = SKILLS.get_dir("style-guides") {
for file in dir.files() {
let is_md = file
.path()
.extension()
.map(|e| e == "md")
.unwrap_or(false);
let is_md = file.path().extension().map(|e| e == "md").unwrap_or(false);
if !is_md {
continue;
}
@ -165,7 +161,11 @@ mod tests {
fn registry_loads_the_style_guides() {
let reg = style_guide_registry();
// ~50 style guides ship with the corpus.
assert!(reg.len() >= 40, "style-guide registry too small: {}", reg.len());
assert!(
reg.len() >= 40,
"style-guide registry too small: {}",
reg.len()
);
assert!(reg.iter().all(|g| !g.name.is_empty()));
}

View file

@ -48,22 +48,58 @@ pub fn build_style_mapping(from: &StyleGuideValues, to: &StyleGuideValues) -> Pr
let mut out = PropertyReplacement::default();
// Fill colours — backgrounds, surfaces, accents.
push_color(&mut out.fill_color, &from.colors.background, &to.colors.background);
push_color(&mut out.fill_color, &from.colors.surface, &to.colors.surface);
push_color(
&mut out.fill_color,
&from.colors.background,
&to.colors.background,
);
push_color(
&mut out.fill_color,
&from.colors.surface,
&to.colors.surface,
);
push_color(&mut out.fill_color, &from.colors.accent, &to.colors.accent);
// Text colours.
push_color(&mut out.text_color, &from.colors.text_primary, &to.colors.text_primary);
push_color(&mut out.text_color, &from.colors.text_secondary, &to.colors.text_secondary);
push_color(&mut out.text_color, &from.colors.text_muted, &to.colors.text_muted);
push_color(
&mut out.text_color,
&from.colors.text_primary,
&to.colors.text_primary,
);
push_color(
&mut out.text_color,
&from.colors.text_secondary,
&to.colors.text_secondary,
);
push_color(
&mut out.text_color,
&from.colors.text_muted,
&to.colors.text_muted,
);
// Border / stroke colour.
push_color(&mut out.stroke_color, &from.colors.border, &to.colors.border);
push_color(
&mut out.stroke_color,
&from.colors.border,
&to.colors.border,
);
// Font families.
push_color(&mut out.font_family, &from.typography.display_font, &to.typography.display_font);
push_color(&mut out.font_family, &from.typography.body_font, &to.typography.body_font);
push_color(&mut out.font_family, &from.typography.data_font, &to.typography.data_font);
push_color(
&mut out.font_family,
&from.typography.display_font,
&to.typography.display_font,
);
push_color(
&mut out.font_family,
&from.typography.body_font,
&to.typography.body_font,
);
push_color(
&mut out.font_family,
&from.typography.data_font,
&to.typography.data_font,
);
// Corner radii (scalar px).
for (f, t) in [

View file

@ -119,7 +119,10 @@ fn ordered(norm: &str, a: &str, b: &str) -> bool {
fn extract_colors(sections: &HashMap<String, String>) -> StyleColors {
let empty = String::new();
let color_section = sections.get("color system").unwrap_or(&empty).to_lowercase();
let color_section = sections
.get("color system")
.unwrap_or(&empty)
.to_lowercase();
let background = hex_near(&color_section, |l| {
l.contains("page background") || ordered(l, "root", "background")
@ -352,7 +355,10 @@ Button / Input: 8px
#[test]
fn extracts_typography_by_role() {
let v = extract_style_guide_values(SAMPLE);
assert_eq!(v.typography.display_font.as_deref(), Some("Playfair Display"));
assert_eq!(
v.typography.display_font.as_deref(),
Some("Playfair Display")
);
assert_eq!(v.typography.body_font.as_deref(), Some("Inter"));
assert_eq!(v.typography.data_font.as_deref(), Some("JetBrains Mono"));
}

View file

@ -192,10 +192,8 @@ impl<'a> AIChatPlaceholder<'a> {
let height = crate::widgets::ai_chat_model_picker::picker_content_height(
&self.state.available_models,
);
let toolbar_top = input_rect.origin.y
+ INPUT_AREA_HEIGHT
+ self.attachment_row_h()
+ CONTROLS_ROW_HEIGHT;
let toolbar_top =
input_rect.origin.y + INPUT_AREA_HEIGHT + self.attachment_row_h() + CONTROLS_ROW_HEIGHT;
let bottom = toolbar_top - 4.0;
Rect {
origin: Point2D::new(rect.origin.x + PAD, bottom - height),
@ -502,10 +500,7 @@ impl<'a> Widget for AIChatPlaceholder<'a> {
let attach_h = self.attachment_row_h();
if attach_h > 0.0 {
let attach_rect = Rect {
origin: Point2D::new(
input_rect.origin.x,
input_rect.origin.y + INPUT_AREA_HEIGHT,
),
origin: Point2D::new(input_rect.origin.x, input_rect.origin.y + INPUT_AREA_HEIGHT),
size: Point2D::new(input_rect.size.x, attach_h),
};
paint_attachment_row(cx, &self.theme, attach_rect, self.state);
@ -523,8 +518,7 @@ impl<'a> Widget for AIChatPlaceholder<'a> {
// Bottom toolbar — model picker on the left, send on the
// right (mirrors the TS panel's bottom row).
let toolbar_y =
input_rect.origin.y + INPUT_AREA_HEIGHT + attach_h + CONTROLS_ROW_HEIGHT;
let toolbar_y = input_rect.origin.y + INPUT_AREA_HEIGHT + attach_h + CONTROLS_ROW_HEIGHT;
let toolbar_center_y = toolbar_y + INPUT_TOOLBAR_HEIGHT / 2.0;
// Model chip — brand logo of the selected model's provider
// + its display name + a chevron. Click toggles the picker.
@ -583,8 +577,8 @@ impl<'a> Widget for AIChatPlaceholder<'a> {
};
// A turn is sendable with text, with staged attachments, or
// both (TS parity: an attachment-only message is valid).
let send_active = !self.state.input.trim().is_empty()
|| !self.state.pending_attachments.is_empty();
let send_active =
!self.state.input.trim().is_empty() || !self.state.pending_attachments.is_empty();
let (send_bg, icon_color) = if send_active {
(self.theme.primary, self.theme.primary_foreground)
} else {
@ -673,7 +667,8 @@ mod tests {
/// Y-coordinate of the bottom toolbar's vertical center.
fn toolbar_center_y() -> f32 {
AI_CHAT_HEIGHT - INPUT_BASE_HEIGHT + 1.0
AI_CHAT_HEIGHT - INPUT_BASE_HEIGHT
+ 1.0
+ INPUT_AREA_HEIGHT
+ CONTROLS_ROW_HEIGHT
+ INPUT_TOOLBAR_HEIGHT / 2.0

View file

@ -190,7 +190,12 @@ fn paint_chip(cx: &mut PaintCx<'_>, theme: &Theme, rect: Rect, icon: Icon, label
}
/// Paint the controls strip — thinking / effort / attach.
pub fn paint_controls_row(cx: &mut PaintCx<'_>, theme: &Theme, controls_rect: Rect, state: &ChatState) {
pub fn paint_controls_row(
cx: &mut PaintCx<'_>,
theme: &Theme,
controls_rect: Rect,
state: &ChatState,
) {
let layout = controls_layout(controls_rect);
paint_chip(
cx,
@ -314,10 +319,7 @@ mod tests {
let rects = attachment_chip_rects(r, 4);
// Click the third chip.
let third = rects[2];
let p = Point2D::new(
third.origin.x + 10.0,
third.origin.y + third.size.y / 2.0,
);
let p = Point2D::new(third.origin.x + 10.0, third.origin.y + third.size.y / 2.0);
assert_eq!(
attachment_row_hit(r, p, 4),
Some(AIChatHit::RemoveAttachment(2))

View file

@ -335,9 +335,10 @@ pub fn paint_node(
// outline.
let stroke = match node.stroke {
Some(s) => Some((s.color, s.width * zoom)),
None if !filled => {
Some((node.fill.unwrap_or(crate::Color::BLACK), (1.5_f32).max(zoom)))
}
None if !filled => Some((
node.fill.unwrap_or(crate::Color::BLACK),
(1.5_f32).max(zoom),
)),
None => None,
};
if let Some((color, width)) = stroke {

View file

@ -112,7 +112,9 @@ async fn run_acp_turn(
Some(n) => n,
None => {
let _ = tx
.send(ChatDelta::Error("acp: notification channel unavailable".into()))
.send(ChatDelta::Error(
"acp: notification channel unavailable".into(),
))
.await;
let _ = tx
.send(ChatDelta::Done {

View file

@ -123,9 +123,7 @@ pub fn write_temp_attachments(attachments: &[ChatAttachment]) -> io::Result<Temp
/// default behaviour.
pub fn thinking_directive(mode: ThinkingMode) -> Option<&'static str> {
match mode {
ThinkingMode::Enabled => {
Some("Think step by step and reason carefully before answering.")
}
ThinkingMode::Enabled => Some("Think step by step and reason carefully before answering."),
ThinkingMode::Disabled => {
Some("Answer directly and concisely, without extended reasoning.")
}

View file

@ -173,9 +173,7 @@ pub fn run_cli_if_requested() -> bool {
std::process::exit(2);
};
let Ok(port) = port_arg.parse::<u16>() else {
eprintln!(
"openpencil-desktop --mcp-http: <port> must be a u16, got {port_arg:?}"
);
eprintln!("openpencil-desktop --mcp-http: <port> must be a u16, got {port_arg:?}");
std::process::exit(2);
};
let Some(path) = args.next() else {

View file

@ -468,7 +468,10 @@ impl WidgetHostNative {
}
// ~7 screen-px grab radius, expressed in doc space.
let r2 = 49.0 / (zoom * zoom);
for (handle, p) in handles {
// Reverse paint order so the topmost-painted handle wins —
// on a full-sweep ellipse the Start + Sweep handles coincide,
// and Sweep is painted last, so it must hit-test first.
for (handle, p) in handles.into_iter().rev() {
let dx = doc_point.x - p.x;
let dy = doc_point.y - p.y;
if dx * dx + dy * dy <= r2 {

View file

@ -800,8 +800,11 @@ fn parse_hex(s: &str) -> Option<[f32; 4]> {
fn short_src(src: &str) -> String {
let s = src.rsplit('/').next().unwrap_or(src);
if s.len() > 24 {
format!("{}…", &s[..24])
// Truncate by characters, not bytes — a `&s[..24]` byte slice
// panics when byte 24 lands inside a multi-byte UTF-8 char.
if s.chars().count() > 24 {
let head: String = s.chars().take(24).collect();
format!("{head}…")
} else {
s.to_string()
}