You are a constitutional council ranking individual git commits for ownership allocation. Compare these two commits. Decide which contributed more lasting value to the project. Judge substance, not spectacle: - Prefer correct, lasting design and real bugfixes over churn, formatting, renames, or generated noise. - Prefer clarity and necessity over sheer line count. A small precise change can beat a large diffuse one. - Do not favor a side merely because its patch is longer or noisier. - Weight what the change does for the project, not the contributor's name. Return ONLY a JSON object: {"winner": "A" or "B", "ratio": "N:M", "explanation": "..."} The explanation must cite concrete differences in the patches (1-3 sentences). Side A — contributor: tommy-mor Side A — commit message: [d61c0469] ? Side A — unified diff (full patch): diff --git a/server/static/sorter.css b/server/static/sorter.css new file mode 100644 index 0000000000000000000000000000000000000000..f5fb317a2c2a86d3dd00a639fff017fa3b8cc354 --- /dev/null +++ b/server/static/sorter.css @@ -0,0 +1,168 @@ +:root { + --bg: #0f1115; + --fg: #e8eaed; + --muted: #9aa0a6; + --accent: #7cacf8; + --panel: #1a1d24; + --border: #2a2f3a; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + background: var(--bg); + color: var(--fg); + line-height: 1.5; +} + +.muted { + color: var(--muted); +} + +.small { + font-size: 0.875rem; +} + +.view-meta { + position: fixed; + top: 0.5rem; + right: 0.5rem; + font-size: 0.75rem; +} + +.demo-panel { + max-width: 40rem; + margin: 4rem auto 2rem; + padding: 2rem; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; +} + +.demo-panel h1 { + margin-top: 0; +} + +.btn-primary { + background: var(--accent); + color: #0f1115; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + font-size: 1rem; + cursor: pointer; +} + +.btn-primary:hover { + filter: brightness(1.1); +} + +code { + font-size: 0.85em; + background: var(--bg); + padding: 0.1em 0.35em; + border-radius: 3px; +} + +.vote-fields { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1rem; +} + +.vote-fields label { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.vote-fields input { + padding: 0.4rem 0.5rem; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); +} + +.rank-list { + margin: 0; + padding-left: 0; + list-style: none; +} + +.rank-list li { + margin-bottom: 0.35rem; +} + +.rank-num { + color: var(--muted); +} + +#parser-input { + width: 100%; + padding: 0.5rem; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + font-size: 1rem; + font-family: inherit; + resize: vertical; +} + +#parser-form .btn-primary { + margin-top: 0.5rem; +} + +.breadcrumbs { + font-size: 0.875rem; + margin-bottom: 1rem; + color: var(--muted); +} + +.breadcrumbs a { + color: var(--accent); + text-decoration: none; +} + +.breadcrumbs a:hover { + text-decoration: underline; +} + +.breadcrumbs .separator { + color: var(--muted); +} + +.rank-list a { + color: var(--accent); + text-decoration: none; +} + +.rank-list a:hover { + text-decoration: underline; +} + +.entity-card h2 { + margin-top: 0; +} + +.scope-name { + color: var(--accent); + font-weight: 600; +} + +.rank-heading { + margin: 0.75rem 0 0.25rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.rank-gap { + text-align: center; + margin: 0.25rem 0; +} Side B — contributor: tommy-mor Side B — commit message: [d63870bc] fix Side B — unified diff (full patch): diff --git a/server/src/parser.rs b/server/src/parser.rs index 4bf201008857e3082b70a55e3dcbb2502ab3ed22..ea437c4434045b44cba04a2b3416df850db518e7 100644 --- a/server/src/parser.rs +++ b/server/src/parser.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use std::rc::Rc; -use std::cell::RefCell; +use std::sync::OnceLock; + use crate::parser_action::{GuideOption, ParserAction, ScrollingSuggestion, Suggestion}; // --- Core Abstractions --- @@ -98,8 +98,8 @@ pub struct Edge { description: Option<&'static str>, } -/// Handler function for generating UI actions -type Handler = Box) -> ParserAction>; +/// Handler function for generating UI actions (Send + Sync so the graph can live in `OnceLock`). +type Handler = Box) -> ParserAction + Send + Sync>; /// Node in the graph pub struct Node { @@ -109,16 +109,16 @@ pub struct Node { handler: Option, } -/// The composable parser graph +/// The composable parser graph (immutable after `build`). pub struct Graph { - nodes: HashMap>>, + nodes: HashMap, root: NodeId, } // --- Graph Builder (Fluent API) --- pub struct GraphBuilder { - nodes: HashMap>>, + nodes: HashMap, current_node: Option, root: NodeId, } @@ -126,31 +126,29 @@ pub struct GraphBuilder { impl GraphBuilder { pub fn new() -> Self { let mut nodes = HashMap::new(); - let root_node = Rc::new(RefCell::new(Node { - id: "root", - edges: Vec::new(), - handler: None, - })); - nodes.insert("root", root_node); - + nodes.insert( + "root", + Node { + id: "root", + edges: Vec::new(), + handler: None, + }, + ); + GraphBuilder { nodes, current_node: Some("root"), root: "root", } } - + /// Select a node to add edges to pub fn at(mut self, node_id: NodeId) -> Self { - // Create node if it doesn't exist - if !self.nodes.contains_key(node_id) { - let node = Rc::new(RefCell::new(Node { - id: node_id, - edges: Vec::new(), - handler: None, - })); - self.nodes.insert(node_id, node); - } + self.nodes.entry(node_id).or_insert_with(|| Node { + id: node_id, + edges: Vec::new(), + handler: None, + }); self.current_node = Some(node_id); self } @@ -161,39 +159,39 @@ impl GraphBuilder { } /// Add an edge with description - pub fn edge_with_desc(mut self, pattern: EdgePattern, target: NodeId, desc: Option<&'static str>) -> Self { + pub fn edge_with_desc( + mut self, + pattern: EdgePattern, + target: NodeId, + desc: Option<&'static str>, + ) -> Self { let current = self.current_node.expect("No current node selected"); - - // Create target node if it doesn't exist - if !self.nodes.contains_key(target) { - let node = Rc::new(RefCell::new(Node { - id: target, - edges: Vec::new(), - handler: None, - })); - self.nodes.insert(target, node); - } - - // Add edge to current node - if let Some(node) = self.nodes.get(current) { - node.borrow_mut().edges.push(Edge { + + self.nodes.entry(target).or_insert_with(|| Node { + id: target, + edges: Vec::new(), + handler: None, + }); + + if let Some(node) = self.nodes.get_mut(current) { + node.edges.push(Edge { pattern, target, description: desc, }); } - + self } - + /// Set handler for current node - pub fn handler(self, handler: F) -> Self - where - F: Fn(&str, &str, &HashMap) -> ParserAction + 'static + pub fn handler(mut self, handler: F) -> Self + where + F: Fn(&str, &str, &HashMap) -> ParserAction + Send + Sync + 'static, { let current = self.current_node.expect("No current node selected"); - if let Some(node) = self.nodes.get(current) { - node.borrow_mut().handler = Some(Box::new(handler)); + if let Some(node) = self.nodes.get_mut(current) { + node.handler = Some(Box::new(handler)); } self } @@ -225,24 +223,25 @@ impl Graph { } fn parse_recursive(&self, state: &mut ParserState) -> ParserAction { - let node = self.nodes.get(state.current_node_id) + let node = self + .nodes + .get(state.current_node_id) .expect("Node not found in graph"); - let node_ref = node.borrow(); - + // If we've consumed all input, check for handler or suggestions if state.cursor >= state.input.len() { - if let Some(handler) = &node_ref.handler { + if let Some(handler) = &node.handler { return handler(&state.original_query, &state.current_prefix, &state.context); } - + // No handler, try to suggest based on available edges - return self.suggest_from_edges(&node_ref, state); + return self.suggest_from_edges(node, state); } - + let remaining = &state.input[state.cursor..]; - + // Try to match each edge - for edge in &node_ref.edges { + for edge in &node.edges { if let Some((consumed, captured)) = edge.pattern.matches(remaining) { // Save state for potential backtracking let saved_cursor = state.cursor; @@ -297,7 +296,7 @@ impl Graph { } // No edges matched - try to provide suggestions - self.suggest_from_edges(&node_ref, state) + self.suggest_from_edges(node, state) } fn suggest_from_edges(&self, node: &Node, state: &ParserState) -> ParserAction { @@ -692,10 +691,15 @@ pub fn build_reddit_graph() -> Graph { // --- Public API --- +static REDDIT_GRAPH: OnceLock = OnceLock::new(); + +fn reddit_graph() -> &'static Graph { + REDDIT_GRAPH.get_or_init(build_reddit_graph) +} + /// Parse a query string and return a UI action pub fn parse_reddit_url(query: &str) -> ParserAction { - let graph = build_reddit_graph(); - graph.parse(query) + reddit_graph().parse(query) } #[cfg(test)]