constitution · epochs · watch · epoch 3

comparison

c_b447fc224eaf (tommy-mor) vs c_257b57c092b1 (tommy-mor)

download prompt · raw event · cmp_66a73080b947b3

council reasoning

~anthropic/claude-sonnet-latest · winner B · 70:30 · permalink

Side B makes a substantive engineering improvement: it removes unnecessary Rc<RefCell<>> indirection in favor of direct HashMap ownership, adds Send+Sync bounds, and caches the graph in a OnceLock instead of rebuilding it on every parse call, improving both performance and correctness. Side A is purely additive CSS styling with no logic changes, useful but lower-impact and more superficial than B's architectural fix to the parser's core data structures.

~x-ai/grok-latest · winner B · 1:4 · permalink

B refactors the core parser graph from Rc<RefCell<Node>> to owned Nodes with Send+Sync handlers and OnceLock caching, fixing rebuild-on-every-parse overhead and enabling safe sharing; A only adds a new static CSS file of presentational rules with no logic or bugfix.

openai/gpt-chat-latest · winner B · 8:1 · permalink

Side B makes a substantive architectural improvement by replacing the parser graph's Rc<RefCell>-based mutable structure with an immutable HashMap, requiring handlers to be Send + Sync, and caching the graph in a OnceLock so it is built once instead of on every parse. Side A only adds a new CSS stylesheet with presentation rules, which improves appearance but does not change core behavior or fix functionality.

sides

A — c_b447fc224eaf (tommy-mor)

message

[d61c0469] ?

diff preview

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;
+}

download full diff A

B — c_257b57c092b1 (tommy-mor)

message

[d63870bc] fix

diff preview

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<dyn Fn(&str, &str, &HashMap<String, String>) -> ParserAction>;
+/// Handler function for generating UI actions (Send + Sync so the graph can live in `OnceLock`).
+type Handler = Box<dyn Fn(&str, &str, &HashMap<String, String>) -> ParserAction + Send + Sync>;
 
 /// Node in the graph
 pub struct Node {
@@ -109,16 +109,16 @@ pub struct Node {
     handler: Option<Handler>,
 }
 
-/// The composable parser graph
+/// The composable parser graph (immutable after `build`).
 pub struct Graph {
-    nodes: HashMap<NodeId, Rc<RefCell<Node>>>,
+    nodes: HashMap<NodeId, Node>,
     root: NodeId,
 }
 
 // --- Graph Builder (Fluent API) ---
 
 pub struct GraphBuilder {
-    nodes: HashMap<NodeId, Rc<RefCell<Node>>>,
+    nodes: HashMap<NodeId, Node>,
     current_node: Option<NodeId>,
     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<F>(self, handler: F) -> Self 
-    where 
-        F: Fn(&str, &str, &HashMap<String, String>) -> ParserAction + 'static
+    pub fn handler<F>(mut self, handler: F) -> Self
+    where
+        F: Fn(&str, &str, &HashMap<String, String>) -> 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<Graph> = 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)]

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.